



  

<!DOCTYPE html>
<html
  lang="en"
  
  data-color-mode="auto" data-light-theme="light" data-dark-theme="dark"
  data-a11y-animated-images="system" data-a11y-link-underlines="true"
  
  >




  <head>
    <meta charset="utf-8">
  <link rel="dns-prefetch" href="https://github.githubassets.com">
  <link rel="dns-prefetch" href="https://avatars.githubusercontent.com">
  <link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
  <link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
  <link rel="preconnect" href="https://github.githubassets.com" crossorigin>
  <link rel="preconnect" href="https://avatars.githubusercontent.com">

<script type="importmap">{"imports":{"react":"https://github.githubassets.com/assets/react-e27d1b3e03961e68.js","react-dom":"https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js","react-dom/client":"https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js","react-is":"https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js","react-reconciler":"https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js","react/compiler-runtime":"https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js","react/jsx-dev-runtime":"https://github.githubassets.com/assets/react-jsx-dev-runtime-ea55d68667d559e5.js","react/jsx-runtime":"https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js","scheduler":"https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js"}}</script>
<meta name="react-profiling" content="0" data-turbo-transient="true" />
<meta name="react-import-map" content="react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@777f63f87cd0" data-turbo-track="reload" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-e27d1b3e03961e68.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js" />

  


  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="light" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link data-color-theme="light_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link data-color-theme="light_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-0f910d8806d5761b.css" /><link data-color-theme="light_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind_high_contrast-a7abd4d4c49ac593.css" /><link data-color-theme="light_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-5fc4c4a9cb41e596.css" /><link data-color-theme="light_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia_high_contrast-ed0cee9214dedabd.css" /><link data-color-theme="dark" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link data-color-theme="dark_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="dark_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-78a2491d538dab5a.css" /><link data-color-theme="dark_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind_high_contrast-c26b63d7c532d0a2.css" /><link data-color-theme="dark_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-a6f60cea68c08405.css" /><link data-color-theme="dark_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia_high_contrast-f66faf28b2ea18b6.css" /><link data-color-theme="dark_dimmed" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-6701f6218c53cf5b.css" /><link data-color-theme="dark_dimmed_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed_high_contrast-7037fa46ae124d97.css" />

  <style type="text/css">
    :root {
      --tab-size-preference: 4;
    }

    pre, code {
      tab-size: var(--tab-size-preference);
    }
  </style>

    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-97df7784617ce1ea.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-9be9fe6313f476af.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-62747e27e61258dc.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/github-7a418ea859d1654d.css" />
  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/repository-72bd7d974b5ac1cc.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-4c5c0895f723f870.css" />

  

  <script type="application/json" id="client-env">{"locale":"en","featureFlags":["actions_caches_react_shell","actions_enable_background_steps","actions_new_hosted_runner_image_select_sizes_and_versions","actions_runners_react_shell","activity_diff_file_tree","activity_repos_file_tree","activity_repos_overview_header","activity_repos_overview_sidebar","agent_author_search_expansion","agent_author_search_expansion_ui_pulls","alternate_user_config_repo","async_conversion_coverage_enabled","billing_billable_licenses_cost_center_bucket_fix","billing_budget_expiration","billing_cost_center_list_assigned_resources","billing_discount_threshold_notification","code_quality_enablement_banner_targeting","code_quality_remove_preview","code_view_raf_sticky_lines","codespaces_prebuild_region_target_update","coding_agent_third_party_model_ui","copilot_agent_snippy","copilot_api_agentic_issue_marshal_yaml","copilot_automation_repo_mcp_servers","copilot_automations_pagination","copilot_base_model_policy_row","copilot_chat_auto_mode_v2","copilot_chat_clear_model_selection_for_default_change","copilot_chat_early_task_provisioning","copilot_chat_persist_session_drafts","copilot_chat_vision_dotcom_chat_ga_gate","copilot_css_textarea_autosize","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_removal_on_marketplace","copilot_fix_failed_workflows_all_skus","copilot_hide_hovercard","copilot_immersive_task_hyperlinking","copilot_mc_cli_resume_any_users_task","copilot_mission_control_agent_merge_fix_ci","copilot_mission_control_agent_merge_resolve_conflicts","copilot_mission_control_awps_batching","copilot_mission_control_early_stop","copilot_mission_control_managed_sandbox_environments","copilot_mission_control_needs_attention","copilot_mission_control_reasoning_effort","copilot_mission_control_sandbox_remote_bypass","copilot_mission_control_task_alive_updates","copilot_mission_control_task_sharing","copilot_org_policy_page_focus_mode","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_swe_agent_authorization_status_ui","copilot_swe_agent_automation_resource_scoped_writes","copilot_swe_agent_discussion_comment_trigger","copilot_swe_agent_discussion_opened_trigger","copilot_swe_agent_discussion_updated_trigger","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_issue_assigned_trigger","copilot_swe_agent_issue_comment_trigger","copilot_swe_agent_issue_labeled_trigger","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_pull_request_assigned_trigger","copilot_swe_agent_pull_request_comment_trigger","copilot_swe_agent_pull_request_labeled_trigger","copilot_swe_agent_pull_request_merged_trigger","copilot_swe_agent_pull_request_opened_trigger","copilot_swe_agent_pull_request_ready_for_review_trigger","copilot_swe_agent_pull_request_review_requested_trigger","copilot_swe_agent_pull_request_review_submitted_trigger","copilot_swe_agent_pull_request_synchronize_trigger","copilot_swe_agent_sub_issue_added_trigger","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_task_scoped_alive_channel","copilot_token_based_billing","copilot_unconfigured_is_inherited","copilot_user_can_upgrade_plan_field","copilot_workbench_sunset_redirect","dashboard_agents_module_auth_token_check","dashboard_indexeddb_caching","dashboard_lists_max_age_filter","fgpat_permissions_selector_redesign","glc_code_quality_repo_settings_workflow_config","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","in_product_messaging_datadog_monitoring","ipm_ubb_individual_budget_banner","issue_fields_multi_select","issue_inline_avatars","issue_pinned_views","issue_pinned_views_team_vs_personal","issue_relative_time_micro","issues_dashboard_sso_structured_errors","issues_expanded_file_types","issues_hide_closed_sub_issues","issues_index_data_router","issues_lazy_load_comment_box_suggestions","issues_react_chrome_container_query_fix","labels_archiving","labels_archiving_info","landing_pages_ninetailed","lifecycle_label_name_updates","marketing_cookie_consent_banner","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_queue_restricted_pushers_warning","merge_status_header_feedback","milestone_closed_issues_prioritization","milestone_show_data_router","octocaptcha_origin_optimization","primer_react_css_anchor_positioning","primer_react_merged_forwarded_refs","prs_copilot_app_open_action","prs_css_anchor_positioning","pull_request_copilot_attribution_header","pull_request_overview_merge_control","pull_request_overview_panel_edit_description","pull_request_persister","pull_request_stacks_navigation_shortcuts","pull_request_virtualization_image_estimate","pull_request_virtualization_loader_batching","pull_request_virtualization_scroll_compensation","pull_request_virtualization_scroll_intent","quick_search_lazy_suggestions","react_blob_isolate_code_lines","react_blob_ssr_content_visibility","react_data_router_tanstack_allowed","react_logged_out_repository_header","react_query_props_with_key","react_sandbox_future_tanstack","repo_app_turbo","repo_issues_sidebar_layout","repo_pulls_dashboard_declutter","repo_pulls_dashboard_ga","repo_pulls_dashboard_persistence","repo_pulls_dashboard_sidebar_links","repos_contributors_limited_default_range","review_involves_filter","rule_ignored_file_paths","rulesets_actor_list_editor","sample_network_conn_type","security_center_artifact_filters_popover","see_who_reacted","semantic_similarity_duplicate_issue_detection","session_logs_ungroup_reasoning_text","set_sha256_on_repo_creation_form","site_banner_desktop_copilot_app","site_ghca_pixel_mona","site_github_app_ga_page","site_github_app_ga_page_highlight","site_github_app_mobile_native_share","site_global_banner_dev_days_attendee","site_global_nav_spark_models_removed","speculation_rules_ui_service","suggest_custom_property_values_copilot","suppress_automated_browser_vitals","swp_forms_disable_octocaptcha","thread_resolution_reason","update_issue_suggestions","viewscreen_sandbox","warn_inaccessible_attachments","webp_support","workstream_plugin_bootstrap"],"githubDomain":"https://github.com","copilotApiOverrideUrl":"https://api.githubcopilot.com","cmcApiUrl":"https://api.github.com/cmc_internal/api"}</script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/high-contrast-cookie-b752d992928a526c.js"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wp-runtime-8b4dcdc2cb6562ff.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-foundation-2f08c823163591a8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-runtime-633ad94fa06c334b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fetch-utilities-0fb4c544a797ec0d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ser-7c872b58ac9f1b39.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tp-7e9817cf5c068979.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/environment-8bdc8c2592df853b.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-runtime.bef03fb24bb03be8.module.css" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/catalyst-52ed81112a548e10.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/selector-observer-e8810f64c443fb9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/relative-time-element-0417f617ad744a0d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/by-9da36843ba9d191d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ja9-9a1160b939f2cd52.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/3h-55903cf2303047ad.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hk-f1498fec45311acd.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jz5-7aa1ff0a0e750f41.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hj-1d800d09e9a8720a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j0-9697c1c8f5cbf523.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/github-elements-3f257b2433f975f0.js" defer="defer"></script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/element-registry-95b267513b87193c.js" defer="defer"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/runtime-helpers-5e0b3ae3c4036207.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/aria-live-524a06aa946df6fc.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hotkey-b1ee53c2200b172a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-core-7bf7b9d689fe3d10.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a9-ca3bc10b816bcc1f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ur-21ec1e439ee2d87c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4t-4c39f7195fc42cc8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pb-53cfff089eaac51b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/6n-afcf86ccabc4cad2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zj-4d9cf83cf08e1643.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wo-6991af5b3829ffe8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/nfv-a6c14301d3ea6d67.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/b5-a2fde30ce0c692b9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sw-9d0489dbf9a9c942.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ttf-7d5abe1b447ced2c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sj-c7a44023ee90d468.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/0pd-c46d8dfae89c2b64.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/behaviors-2a35428577a46a82.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/react-core.255cab819a92f7e1.module.css" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-menu-baecde86e879facb.js" defer="defer"></script>
  
  <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/primer-react-8503588299651923.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ncx-66e64888bc4615bb.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/qmp-c7cfba40cf5a93b0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j6-148a74299e5a01ab.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/t7l-a9404625ec30bbc7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4u-8f9f1d6be01d6a22.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s0-68d09e5e8a80cb94.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/y5-0cb3f75cec297941.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/n4u-cd9039ad946274ad.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j8s-00bd469c4017a543.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/la-27679061f16e1495.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iu-2d03f5e6d4fdd54d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/gru-2b0fac8887ff8c57.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a0-234e967e7c6d22ce.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/gz3-5bdfbfceac967bb6.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/15m-64f3034430471048.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/1e-c15441bdb94d54ae.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/gzu-6ad9a0527a5d7afd.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/bi-3a42b8714e06c4d0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tu1-37e16844272ddefa.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/b1-ac66ededed061993.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/gmn-1e5497c041b66231.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/0l5-d1b2a63015b5eb6d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/c2-e85522be441acf73.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/f9-baf0a4193779952e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sp-87dce99c82a27004.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zkm-116b01b8485bbe3f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/og-518d12c9e011601d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/b9-947db365ea875cfa.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/14-8c7b4ad3c845664c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/dsj-2e7d883cb843e9e6.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ec-38012819307fd539.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/du-6607da3b8b579202.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/20-f43eb09c2be69457.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ql0-107312645ac2b72d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wp-fa7cdc0f7656d876.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p0l-fe491d943adf7508.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/v1e-d099d9fd21ad25e5.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fn-7b298a45c55fb6fb.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7cw-6e493a20eaaa04b5.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ok-290e4b9465ba9c5b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/6d-ca6d584a0fd6368c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/y8-e2f18f5a24733f3b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/xc-3fd4a2e7dcbad8e2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vjg-2874f95f389c4d3f.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-view-b187f9df797cfbc1.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.e4334163c54cccf5.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/ql0.501e99879e15a48e.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/xc.de2b1dde0494f261.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-view.30fafa560a1eee5e.module.css" />


  <title>2dph/PLAN.md at main · eSlider/2dph · GitHub</title>



  <meta name="route-pattern" content="/:user_id/:repository/blob/*name(/*path)" data-turbo-transient>
  <meta name="route-controller" content="blob" data-turbo-transient>
  <meta name="route-action" content="show" data-turbo-transient>
  <meta name="fetch-nonce" content="v2:6602ea95-d6b4-3474-1a0f-619f24c1b896">

    
  <meta name="current-catalog-service-hash" content="f3abb0cc802f3d7b95fc8762b94bdcb13bf39634c40c357301c4aa1d67a256fb">


  <meta name="request-id" content="9E16:7A93:3999A1:363CA1:6AC74889" data-pjax-transient="true"/><meta name="html-safe-nonce" content="e4bde4af43485ff62b557ae7deb7cbc9cecdaa8d8f8147b37976daede7b3e64e" data-pjax-transient="true"/><meta name="visitor-payload" content="eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5RTE2OjdBOTM6Mzk5OUExOjM2M0NBMTo2QUM3NDg4OSIsInZpc2l0b3JfaWQiOiI2NDg4MTQyOTU1MzY5OTQxMTI5IiwicmVnaW9uX2VkZ2UiOiJmcmEiLCJyZWdpb25fcmVuZGVyIjoiZnJhIn0=" data-pjax-transient="true"/><meta name="visitor-hmac" content="ab204bdb2f1aec768c224b5f047d0b9a69c06103e6fe9397362fabecdd090aeb" data-pjax-transient="true"/>


    <meta name="hovercard-subject-tag" content="repository:1330190220" data-turbo-transient>


  <meta name="github-keyboard-shortcuts" content="repository,source-code,file-tree,copilot" data-turbo-transient="true" />
  

  <meta name="selected-link" value="repo_source" data-turbo-transient>
  <link rel="assets" href="https://github.githubassets.com/">

    <meta name="google-site-verification" content="Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I">

<meta name="octolytics-url" content="https://collector.github.com/github/collect" />





  <meta name="analytics-location" content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-turbo-transient="true" />

  




    <meta name="user-login" content="">

  

    <meta name="viewport" content="width=device-width">

    

      <meta name="description" content="Fact evidence-first brain with property graph + hybrid RAG. Search is deduction. ETL included. - 2dph/PLAN.md at main · eSlider/2dph">

      <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">

    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
    <meta property="fb:app_id" content="1401488693436528">
    <meta name="apple-itunes-app" content="app-id=1477376905, app-argument=https://github.com/eSlider/2dph/blob/main/PLAN.md" />

      <meta name="twitter:image" content="https://opengraph.githubassets.com/89342bd3651d985130373c59d02e321d9314dc504075e0b51b8d3edc35d98365/eSlider/2dph" /><meta name="twitter:site" content="@github" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content="2dph/PLAN.md at main · eSlider/2dph" /><meta name="twitter:description" content="Fact evidence-first brain with property graph + hybrid RAG. Search is deduction. ETL included. - eSlider/2dph" />
  <meta property="og:image" content="https://opengraph.githubassets.com/89342bd3651d985130373c59d02e321d9314dc504075e0b51b8d3edc35d98365/eSlider/2dph" /><meta property="og:image:alt" content="Fact evidence-first brain with property graph + hybrid RAG. Search is deduction. ETL included. - eSlider/2dph" /><meta property="og:image:width" content="1200" /><meta property="og:image:height" content="600" /><meta property="og:site_name" content="GitHub" /><meta property="og:type" content="object" /><meta property="og:title" content="2dph/PLAN.md at main · eSlider/2dph" /><meta property="og:url" content="https://github.com/eSlider/2dph/blob/main/PLAN.md" /><meta property="og:description" content="Fact evidence-first brain with property graph + hybrid RAG. Search is deduction. ETL included. - eSlider/2dph" />
  




      <meta name="hostname" content="github.com">



        <meta name="expected-hostname" content="github.com">


  <meta http-equiv="x-pjax-version" content="8a04ed058e34f1ad63cd2ec184ddc8db8ab22004d2ce8345ee86788432baa4cf" data-turbo-track="reload">
  <meta http-equiv="x-pjax-csp-version" content="c4a65e47b0c850e1157ae8e66298070851d7e3b558038de5a3b4ff7a93e630ed" data-turbo-track="reload">
  <meta http-equiv="x-pjax-css-version" content="1ff3f1d4e1abd61f6f135b47d3b815e2f7169f8dd418f0118c6b1cf4de6cbc86" data-turbo-track="reload">
  <meta http-equiv="x-pjax-js-version" content="31760b6cf01794eaa3898a84a5be66fa89579904959fd0698672e3cbcd9741ca" data-turbo-track="reload">

  <meta name="turbo-cache-control" content="no-preview" data-turbo-transient="">

      <meta name="turbo-cache-control" content="no-cache" data-turbo-transient>

    <meta data-hydrostats="publish">

  <meta name="go-import" content="github.com/eSlider/2dph git https://github.com/eSlider/2dph.git">

  <meta name="octolytics-dimension-user_id" content="1188335" /><meta name="octolytics-dimension-user_login" content="eSlider" /><meta name="octolytics-dimension-repository_id" content="1330190220" /><meta name="octolytics-dimension-repository_nwo" content="eSlider/2dph" /><meta name="octolytics-dimension-repository_public" content="true" /><meta name="octolytics-dimension-repository_is_fork" content="false" /><meta name="octolytics-dimension-repository_network_root_id" content="1330190220" /><meta name="octolytics-dimension-repository_network_root_nwo" content="eSlider/2dph" />
  



    

    <meta name="turbo-body-classes" content="logged-out env-production page-responsive">
  <meta name="disable-turbo" content="false">


  <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">


  <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">

  <meta name="release" content="abcc1c729a3dae04e49a5be4a885c179f1620884" data-turbo-track="reload">
  <meta name="ui-target" content="full">

  <link rel="mask-icon" href="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" color="#000000">
  <link rel="alternate icon" class="js-site-favicon" type="image/png" href="https://github.githubassets.com/favicons/favicon.png">
  <link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon.svg" data-base-href="https://github.githubassets.com/favicons/favicon">

<meta name="theme-color" content="#1e2327">
<meta name="color-scheme" content="light dark" />


  <link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">

  </head>

  <body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
    <div data-turbo-body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
      <div id="__primerPortalRoot__" style="z-index: 1000; position: absolute; width: 100%;" data-turbo-permanent></div>
      

    <div class="position-relative header-wrapper js-header-wrapper ">
      <a href="#start-of-content" data-skip-target-assigned="false" class="px-2 tmp-py-4 color-bg-accent-emphasis color-fg-on-emphasis show-on-focus js-skip-to-content">Skip to content</a>

      <span data-view-component="true" class="progress-pjax-loader Progress position-fixed width-full">
    <span style="width: 0%;" data-view-component="true" class="Progress-item progress-pjax-loader-bar left-0 top-0 color-bg-accent-emphasis"></span>
</span>      
      <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/qc-1bbbe8787ea1cd4b.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog-a3b4cbcefa4e617e.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.e4334163c54cccf5.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog.b8afe3d106c9bb64.module.css" />

<react-partial
  partial-name="keyboard-shortcuts-dialog"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"docsUrl":"https://docs.github.com/get-started/accessibility/keyboard-shortcuts"}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>





      

          <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-install-banner-partial-173a78d38983be1c.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.e4334163c54cccf5.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-install-banner-partial.882c17396978a47d.module.css" />

<react-partial
  partial-name="app-install-banner-partial"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>


          

                <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/2l-e468179ba7123a2e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/rwd-069fc3d9814ff9fe.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fz-e662bc3745e171e7.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jag-3559ee48016881a2.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/3d-7f22b36f29a9dd94.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tua-4998592d21eb16f7.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/8i-ea1b3f2e11ed8cd3.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/marketing-header-b6853fcac2424bda.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.e4334163c54cccf5.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-brand-css.05b219cdb8e80c43.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/8i.5d9e6dcb0dade694.module.css" />

<react-partial
  partial-name="marketing-header"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"color_mode":"dark","logged_in":false,"marketing_page":false,"home_path":"/","login_path":"/login?return_to=https%3A%2F%2Fgithub.com%2FeSlider%2F2dph%2Fblob%2Fmain%2FPLAN.md","signup_path":"/signup?ref_cta=Sign+up\u0026ref_loc=header+logged+out\u0026ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow\u0026source=header-repo\u0026source_repo=eSlider%2F2dph","signup_enabled":true,"is_signup_controller":false,"show_search_and_nav":true,"hide_search":false,"private_mode_enabled":false,"should_use_dotcom_links":true,"auth_hydro_click":"{\"event_type\":\"authentication.click\",\"payload\":{\"location_in_page\":\"site header menu\",\"repository_id\":null,\"auth_type\":\"SIGN_UP\",\"originating_url\":\"https://github.com/eSlider/2dph/blob/main/PLAN.md\",\"user_id\":null}}","auth_hydro_click_hmac":"b8795acf7fee7bcd0eabd0b1212e84deb77cfd3681c661c4ca2b3383f770e245","overlay":false,"fixed":false}}</script>
  <div data-target="react-partial.reactRoot"><div data-color-mode="dark" data-light-theme="light" data-dark-theme="dark"><header class="MarketingHeader-module__root__Tk7n3 HeaderMktg header-logged-out" role="banner" data-marketing-header="true" data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" data-is-top="true"><h2 class="MarketingHeader-module__visuallyHidden__sqKsl">Navigation Menu</h2><button type="button" class="MarketingHeader-module__backdrop__sw4RU" aria-label="Close navigation menu"></button><div class="MarketingHeader-module__bar__mBSyE"><div class="MarketingHeader-module__topRow__yeury"><div class="MarketingHeader-module__toggleSlot__hDxbh"><button type="button" class="HeaderMenuToggle-module__toggle__i8EiC" aria-label="Toggle navigation" aria-expanded="false"><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span></button></div><a href="/" aria-label="Homepage" class="HeaderLogo-module__logo__UFyHI" data-analytics-event="{&quot;action&quot;:&quot;homepage&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;logo&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;homepage_link_logo_header&quot;}"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github" viewBox="0 0 24 24" width="32" height="32" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path></svg></a><div class="AuthCTAs-module__mobileActions__NNzeV"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/login?return_to=https%3A%2F%2Fgithub.com%2FeSlider%2F2dph%2Fblob%2Fmain%2FPLAN.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/eSlider/2dph/blob/main/PLAN.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="b8795acf7fee7bcd0eabd0b1212e84deb77cfd3681c661c4ca2b3383f770e245"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_3dd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_3dd_">Appearance settings</div></div></div><div class="MarketingHeader-module__menu__GIy3y"><div class="MarketingHeader-module__menuWrapper__owstH"><nav class="MarketingNavigation-module__nav__W0KYY" aria-label="Global"><ul class="MarketingNavigation-module__list__tFbMb"><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_nd_">Platform<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5knd_">AI CODE CREATION</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot" data-analytics-event="{&quot;action&quot;:&quot;github_copilot&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>GitHub Copilot</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Write better code with AI</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/ai/github-app" data-analytics-event="{&quot;action&quot;:&quot;github_copilot_app&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_app_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656"></path></svg>GitHub Copilot app</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Direct agents from issue to merge</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/mcp" data-analytics-event="{&quot;action&quot;:&quot;mcp_registry&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;mcp_registry_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mcp NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.52 1.12a3.578 3.578 0 0 1 6.078 2.98 3.578 3.578 0 0 1 2.982 6.08l-3.292 3.293a.252.252 0 0 0 0 .354l.843.843a.749.749 0 1 1-1.06 1.06l-.844-.843a1.75 1.75 0 0 1 0-2.474L13.52 9.12a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0L7.731 9.03A.75.75 0 0 1 6.67 7.97l2.85-2.85a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0l-4.799 4.8A.75.75 0 0 1 .72 5.92Z"></path><path d="M7.52 3.12a.749.749 0 1 1 1.06 1.06L5.731 7.03A2.079 2.079 0 0 0 8.67 9.97l2.85-2.85a.749.749 0 1 1 1.06 1.06l-2.849 2.85A3.578 3.578 0 0 1 4.67 5.97Z"></path></svg>MCP Registry</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Integrate external tools</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9knd_">DEVELOPER WORKFLOWS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/actions" data-analytics-event="{&quot;action&quot;:&quot;actions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;actions_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-workflow NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h3.5C6.216 0 7 .784 7 1.75v3.5A1.75 1.75 0 0 1 5.25 7H4v4a1 1 0 0 0 1 1h4v-1.25C9 9.784 9.784 9 10.75 9h3.5c.966 0 1.75.784 1.75 1.75v3.5A1.75 1.75 0 0 1 14.25 16h-3.5A1.75 1.75 0 0 1 9 14.25v-.75H5A2.5 2.5 0 0 1 2.5 11V7h-.75A1.75 1.75 0 0 1 0 5.25Zm1.75-.25a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Zm9 9a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Z"></path></svg>Actions</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Automate any workflow</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/codespaces" data-analytics-event="{&quot;action&quot;:&quot;codespaces&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;codespaces_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codespaces NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 11.25c0-.966.784-1.75 1.75-1.75h12.5c.966 0 1.75.784 1.75 1.75v3A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm2-9.5C2 .784 2.784 0 3.75 0h8.5C13.216 0 14 .784 14 1.75v5a1.75 1.75 0 0 1-1.75 1.75h-8.5A1.75 1.75 0 0 1 2 6.75Zm1.75-.25a.25.25 0 0 0-.25.25v5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5a.25.25 0 0 0-.25-.25Zm-2 9.5a.25.25 0 0 0-.25.25v3c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3a.25.25 0 0 0-.25-.25Z"></path><path d="M7 12.75a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm-4 0a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1-.75-.75Z"></path></svg>Codespaces</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Instant dev environments</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/issues" data-analytics-event="{&quot;action&quot;:&quot;issues&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;issues_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg>Issues</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Plan and track work</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-review" data-analytics-event="{&quot;action&quot;:&quot;code_review&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_review_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg>Code Review</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Manage code changes</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-quality" data-analytics-event="{&quot;action&quot;:&quot;code_quality&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_quality_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codescan-checkmark NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.28 6.28a.75.75 0 1 0-1.06-1.06L6.25 8.19l-.97-.97a.75.75 0 0 0-1.06 1.06l1.5 1.5a.75.75 0 0 0 1.06 0l3.5-3.5Z"></path><path d="M7.5 15a7.5 7.5 0 1 1 5.807-2.754l2.473 2.474a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2.474-2.473A7.472 7.472 0 0 1 7.5 15Zm0-13.5a6 6 0 1 0 4.094 10.386.748.748 0 0 1 .293-.292 6.002 6.002 0 0 0 1.117-6.486A6.002 6.002 0 0 0 7.5 1.5Z"></path></svg>Code Quality</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enforce quality at merge</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dknd_">APPLICATION SECURITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Find and fix vulnerabilities</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/code-security" data-analytics-event="{&quot;action&quot;:&quot;code_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code-square NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>Code security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Secure your code as you build</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/secret-protection" data-analytics-event="{&quot;action&quot;:&quot;secret_protection&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;secret_protection_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-lock NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"></path></svg>Secret protection</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Stop leaks before they start</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ NavGroup-module__hasSeparator__FnMrN"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_hknd_">EXPLORE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_hknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/why-github" data-analytics-event="{&quot;action&quot;:&quot;why_github&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;why_github_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Why GitHub</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog" data-analytics-event="{&quot;action&quot;:&quot;blog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;blog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Blog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog/changelog" data-analytics-event="{&quot;action&quot;:&quot;changelog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;changelog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Changelog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/marketplace" data-analytics-event="{&quot;action&quot;:&quot;marketplace&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;marketplace_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Marketplace</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/features" data-analytics-event="{&quot;action&quot;:&quot;view_all_features&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_features_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all features</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_17d_">Solutions<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_17d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5l7d_">BY COMPANY SIZE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprises&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprises_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Enterprises</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/team" data-analytics-event="{&quot;action&quot;:&quot;small_and_medium_teams&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;small_and_medium_teams_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Small and medium teams</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise/startups" data-analytics-event="{&quot;action&quot;:&quot;startups&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;startups_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Startups</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/nonprofits" data-analytics-event="{&quot;action&quot;:&quot;nonprofits&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;nonprofits_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Nonprofits</span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9l7d_">BY USE CASE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/app-modernization" data-analytics-event="{&quot;action&quot;:&quot;app_modernization&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;app_modernization_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">App Modernization</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devsecops" data-analytics-event="{&quot;action&quot;:&quot;devsecops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devsecops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevSecOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/ci-cd" data-analytics-event="{&quot;action&quot;:&quot;ci/cd&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ci/cd_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">CI/CD</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/use-case" data-analytics-event="{&quot;action&quot;:&quot;view_all_use_cases&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_use_cases_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all use cases</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dl7d_">BY INDUSTRY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dl7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/healthcare" data-analytics-event="{&quot;action&quot;:&quot;healthcare&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;healthcare_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Healthcare</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/financial-services" data-analytics-event="{&quot;action&quot;:&quot;financial_services&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;financial_services_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Financial services</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/manufacturing" data-analytics-event="{&quot;action&quot;:&quot;manufacturing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;manufacturing_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Manufacturing</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/government" data-analytics-event="{&quot;action&quot;:&quot;government&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;government_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Government</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/industry" data-analytics-event="{&quot;action&quot;:&quot;view_all_industries&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_industries_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all industries</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions" data-analytics-event="{&quot;action&quot;:&quot;view_all_solutions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_solutions_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all solutions</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_1nd_">Resources<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_1nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5lnd_">EXPLORE BY TOPIC</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=ai" data-analytics-event="{&quot;action&quot;:&quot;ai&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ai_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">AI</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=software-development" data-analytics-event="{&quot;action&quot;:&quot;software_development&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;software_development_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Software Development</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=security" data-analytics-event="{&quot;action&quot;:&quot;security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources/articles" data-analytics-event="{&quot;action&quot;:&quot;view_all_topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_topics_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all topics</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9lnd_">EXPLORE BY TYPE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/customer-stories" data-analytics-event="{&quot;action&quot;:&quot;customer_stories&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_stories_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer stories</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/events" data-analytics-event="{&quot;action&quot;:&quot;events__webinars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;events__webinars_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Events &amp; webinars</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/whitepapers" data-analytics-event="{&quot;action&quot;:&quot;ebooks__reports&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ebooks__reports_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Ebooks &amp; reports</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/executive-insights" data-analytics-event="{&quot;action&quot;:&quot;business_insights&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;business_insights_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Business insights</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://skills.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_skills&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_skills_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Skills</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dlnd_">SUPPORT &amp; SERVICES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dlnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://support.github.com" data-analytics-event="{&quot;action&quot;:&quot;customer_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_support_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer support</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/orgs/community/discussions" data-analytics-event="{&quot;action&quot;:&quot;community_forum&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;community_forum_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Community forum</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trust-center" data-analytics-event="{&quot;action&quot;:&quot;trust_center&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trust_center_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trust center</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/partners" data-analytics-event="{&quot;action&quot;:&quot;partners&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;partners_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Partners</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources" data-analytics-event="{&quot;action&quot;:&quot;view_all_resources&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_resources_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all resources</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_27d_">Open Source<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_27d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5m7d_">COMMUNITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/open-source/sponsors" data-analytics-event="{&quot;action&quot;:&quot;github_sponsors&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_sponsors_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sponsor-tiers NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.586 1C12.268 1 13.5 2.37 13.5 4.25c0 1.745-.996 3.359-2.622 4.831-.166.15-.336.297-.509.438l1.116 5.584a.75.75 0 0 1-.991.852l-2.409-.876a.25.25 0 0 0-.17 0l-2.409.876a.75.75 0 0 1-.991-.852L5.63 9.519a13.78 13.78 0 0 1-.51-.438C3.497 7.609 2.5 5.995 2.5 4.25 2.5 2.37 3.732 1 5.414 1c.963 0 1.843.403 2.474 1.073L8 2.198l.112-.125a3.385 3.385 0 0 1 2.283-1.068L10.586 1Zm-3.621 9.495-.718 3.594 1.155-.42a1.75 1.75 0 0 1 1.028-.051l.168.051 1.154.42-.718-3.592c-.199.13-.37.235-.505.314l-.169.097a.75.75 0 0 1-.72 0 9.54 9.54 0 0 1-.515-.308l-.16-.105ZM10.586 2.5c-.863 0-1.611.58-1.866 1.459-.209.721-1.231.721-1.44 0C7.025 3.08 6.277 2.5 5.414 2.5 4.598 2.5 4 3.165 4 4.25c0 1.23.786 2.504 2.128 3.719.49.443 1.018.846 1.546 1.198l.325.21.076-.047.251-.163a13.341 13.341 0 0 0 1.546-1.198C11.214 6.754 12 5.479 12 4.25c0-1.085-.598-1.75-1.414-1.75Z"></path></svg>GitHub Sponsors</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Fund open source developers</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9m7d_">PROGRAMS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://securitylab.github.com" data-analytics-event="{&quot;action&quot;:&quot;security_lab&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_lab_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security Lab</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://maintainers.github.com" data-analytics-event="{&quot;action&quot;:&quot;maintainer_community&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;maintainer_community_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Maintainer Community</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://stars.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_stars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_stars_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Stars</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://archiveprogram.github.com" data-analytics-event="{&quot;action&quot;:&quot;archive_program&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;archive_program_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Archive Program</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dm7d_">REPOSITORIES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dm7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/topics" data-analytics-event="{&quot;action&quot;:&quot;topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;topics_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Topics</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trending" data-analytics-event="{&quot;action&quot;:&quot;trending&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trending_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trending</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/collections" data-analytics-event="{&quot;action&quot;:&quot;collections&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;collections_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Collections</span></a></li></ul></div></li></ul></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_2nd_">Enterprise<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_2nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5mnd_">ENTERPRISE SOLUTIONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprise_platform&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprise_platform_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-stack NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.119 5.456a1.251 1.251 0 0 1 0-2.162ZM8.125 1.69a.248.248 0 0 0-.25 0l-4.63 2.685 4.63 2.685a.248.248 0 0 0 .25 0l4.63-2.685ZM1.601 7.789a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0L1.874 8.814A.75.75 0 0 1 1.6 7.789Zm0 3.5a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0l-5.248-3.044a.75.75 0 0 1-.273-1.025Z"></path></svg>Enterprise platform</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">AI-powered developer platform</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9mnd_">AVAILABLE ADD-ONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade security features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot/copilot-business" data-analytics-event="{&quot;action&quot;:&quot;copilot_for_business&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;copilot_for_business_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>Copilot for Business</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade AI features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise/premium-support" data-analytics-event="{&quot;action&quot;:&quot;premium_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;premium_support_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-comment-discussion NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"></path></svg>Premium Support</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade 24/7 support</span></span></a></li></ul></div></li></ul></div></div></li><li><a href="https://github.com/pricing" data-analytics-event="{&quot;action&quot;:&quot;pricing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;pricing&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;pricing_link_pricing_navbar&quot;}" class="MarketingNavigation-module__navLink__hUomM">Pricing</a></li></ul></nav><div class="MarketingHeader-module__ctaContainer__tBmPz"><div class="HeaderSearch-module__searchSlot__oVOUS"><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderSearch-module__trigger__zsF9q" type="button" aria-haspopup="dialog" aria-expanded="false" aria-label="Search or jump to, type / to search" data-analytics-event="{&quot;action&quot;:&quot;searchbar&quot;,&quot;tag&quot;:&quot;input&quot;,&quot;context&quot;:&quot;global&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;searchbar_input_global_navbar&quot;}"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"><span class="HeaderSearch-module__content__kMpxU"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search HeaderSearch-module__icon__wcrHX" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg><span class="HeaderSearch-module__label__d1iWG">Search</span><kbd class="HeaderSearch-module__kbd__HNG0o" aria-hidden="true">/</kbd></span></span></span></button><div class="d-none"></div></div><div class="AuthCTAs-module__signInWrap__q2P60"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq AuthCTAs-module__desktopActionGap__UZuXT AuthCTAs-module__hiddenBelowLg__BfKBw" href="/login?return_to=https%3A%2F%2Fgithub.com%2FeSlider%2F2dph%2Fblob%2Fmain%2FPLAN.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/eSlider/2dph/blob/main/PLAN.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="b8795acf7fee7bcd0eabd0b1212e84deb77cfd3681c661c4ca2b3383f770e245"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a></div><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--secondary___gHnw_ Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/signup?ref_cta=Sign+up&amp;ref_loc=header+logged+out&amp;ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow&amp;source=header-repo&amp;source_repo=eSlider%2F2dph" data-analytics-event="{&quot;action&quot;:&quot;sign_up&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_up_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/eSlider/2dph/blob/main/PLAN.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="b8795acf7fee7bcd0eabd0b1212e84deb77cfd3681c661c4ca2b3383f770e245"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-secondary___eJ0_a">Sign up</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_fbd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_fbd_">Appearance settings</div></div></div></div></div><div class="MarketingHeader-module__bottomBorder__uZT38" aria-hidden="true"></div></header></div></div>
</react-partial>



      <div hidden="hidden" data-view-component="true" class="js-stale-session-flash stale-session-flash flash flash-warn flash-full">
  
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
        <span class="js-stale-session-flash-signed-in" hidden>You signed in with another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-signed-out" hidden>You signed out in another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-switched" hidden>You switched accounts on another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>

    <button id="icon-button-b985e355-6c7e-4ff3-b401-ae2afad820db" aria-labelledby="tooltip-a8c27dbe-8060-49f4-a40b-5e78f1836984" type="button" data-view-component="true" class="Button Button--iconOnly Button--invisible Button--medium flash-close js-flash-close">  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x Button-visual">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button><tool-tip id="tooltip-a8c27dbe-8060-49f4-a40b-5e78f1836984" for="icon-button-b985e355-6c7e-4ff3-b401-ae2afad820db" popover="manual" data-direction="s" data-type="label" data-view-component="true" class="sr-only position-absolute">Dismiss alert</tool-tip>


  
</div>
    </div>

  <div id="start-of-content" class="show-on-focus"></div>








    <div id="js-flash-container" class="flash-container" data-turbo-replace>






  <template class="js-flash-template">
    
<div class="flash flash-full   {{ className }}">
  <div >
    <button autofocus class="flash-close js-flash-close" type="button" aria-label="Dismiss this message">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    <div aria-atomic="true" role="alert" class="js-flash-alert">
      
      <div>{{ message }}</div>

    </div>
  </div>
</div>
  </template>
</div>


    






  <div
    class="application-main "
    data-commit-hovercards-enabled
    data-discussion-hovercards-enabled
    data-issue-and-pr-hovercards-enabled
    data-project-hovercards-enabled
  >
        <div itemscope itemtype="http://schema.org/SoftwareSourceCode" class="">
    <main id="js-repo-pjax-container" >
      
      








  

    <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ym-1a7ce813a378e1f7.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/mn-bb458474353a279e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/t5-26d19272bbf73ca3.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/2d5-9abb2f6ba5c935d0.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/24-b11e1c4108322883.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/mrn-12afac2ecd2be980.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/dgq-bbf7670f9cdba2e0.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7w-740b4e942974ccba.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/we-c9f93392098ad6c9.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/775-f58305f9bcded6f1.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/bn-fcef0645a47703fc.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/xgw-677b1ad5da5c3c6d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ge-ca1c0ba8656f9680.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/9f-3f61950dd9225c4a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/global-nav-bar-76f3d16d4cab2423.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.e4334163c54cccf5.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/ql0.501e99879e15a48e.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-nav-bar.679bccc49e90d4e2.module.css" />

<react-partial
  partial-name="global-nav-bar"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"contextRegion":{"crumbs":[{"crumb_type":"user","label":"eSlider","is_root":false,"href":"/eSlider"},{"crumb_type":"repository","label":"2dph","is_root":false,"href":"/eSlider/2dph"}],"localNavigation":[{"id":"code","icon":"code","label":"Code","href":"/eSlider/2dph","selectedLinks":["repo_source","repo_downloads","repo_commits","repo_releases","repo_tags","repo_branches","repo_packages","repo_deployments","repo_attestations"],"popoverTarget":false,"commandId":"repositories:go-to-code","reactNav":{"appTarget":"code-view","anchor":"code-view-repo-link"},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"issues","icon":"issue-opened","label":"Issues","href":"/eSlider/2dph/issues","selectedLinks":["repo_issues","repo_labels","repo_milestones"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-issues","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"pull-requests","icon":"git-pull-request","label":"Pull requests","href":"/eSlider/2dph/pulls","selectedLinks":["repo_pulls","checks"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-pull-requests","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"actions","icon":"play","label":"Actions","href":"/eSlider/2dph/actions","selectedLinks":["repo_actions"],"popoverTarget":false,"commandId":"repositories:go-to-actions","reactNav":{"appTarget":"actions-workflows","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"projects","icon":"table","label":"Projects","href":"/eSlider/2dph/projects","selectedLinks":["repo_projects","new_repo_project","repo_project"],"popoverTarget":false,"commandId":"repositories:go-to-projects","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"security-and-quality","icon":"shield","label":"Security and quality","href":"/eSlider/2dph/security","selectedLinks":["security","overview","alerts","policy","token_scanning","code_scanning"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-security","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"insights","icon":"graph","label":"Insights","href":"/eSlider/2dph/pulse","selectedLinks":["repo_graphs","repo_contributors","dependency_graph","dependabot_updates","pulse","people","community"],"popoverTarget":false,"commandId":"repositories:go-to-insights","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}}],"localNavigationUpdateChannel":null,"selectedLink":"repo_source"},"owner":null,"headerLogo":{"href":"/","aria-label":"Homepage "},"notifications":{"indicatorMode":"disabled","websocketChannel":null,"fetchIndicatorSrc":"/notifications/indicator","fetchIndicatorEnabled":false},"issues":{"href":"/issues"},"pulls":{"href":"/pulls"},"contributedRepos":{"href":"/repos"},"copilot":{"show":false,"showAgentsButton":false,"showRelaunchAnnouncement":false,"copilotChatUrl":null,"copilotApiUrl":"https://api.githubcopilot.com"},"search":{"show":true,"showCommandPalette":false,"isSearchPage":false,"isJumpToSearch":false,"searchContext":{"scope":"repo:eSlider/2dph","current_repo_name":"2dph","current_repo_nwo":"eSlider/2dph","user_id":"eSlider"}},"commandPalette":null,"enterpriseBar":{"show":false},"globalTransactionalMessage":[],"payloadsUrl":"/_global-navigation/payloads.json?can_toggle_site_admin_and_employee_status=0\u0026is_admin_mode_on=0\u0026is_ui_opted_out=0\u0026show_ui_opt_out=0\u0026v=6","contextRegionUrl":"/_global-navigation/context-region.json"}}</script>
  <div data-target="react-partial.reactRoot"><header aria-label="Global navigation menu" data-component="Stack" class="GlobalNav styles-module__appHeader__YzYWk prc-Stack-Stack-UQ9k6" data-gap="none" data-direction="vertical" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="none"><div data-component="Stack" class="prc-Stack-Stack-UQ9k6" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="center" data-padding="none"><div data-testid="top-nav-center" data-component="Stack" class="styles-module__center__R3QRv styles-module__withLocalNavigation__rjTJ_ GlobalNavBar-module__loggedOut__Jv1rk prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="normal"><nav class="styles-module__contextRegion__VbSp2 prc-Breadcrumbs-BreadcrumbsBase-3Gb-B" aria-label="Breadcrumbs" data-overflow="menu" data-variant="normal" data-component="Breadcrumbs"><ol class="prc-Breadcrumbs-BreadcrumbsList-BKjpe"><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/eSlider" data-discover="true"><span class="">eSlider</span></a></li><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/eSlider/2dph" data-discover="true"><span class="styles-module__contextCrumbLast__tI2e3">2dph</span></a></li></ol></nav></div><div data-testid="top-nav-right" data-component="Stack" class="styles-module__right__mlBQg styles-module__withLocalNavigation__rjTJ_ styles-module__rightWithResponsiveCreateButton__SKn2W prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="start" data-padding="normal"></div></div><h2 class="prc-src-InternalVisuallyHidden-2YaI6">Repository navigation</h2><nav class="prc-components-UnderlineWrapper-eT-Yj prc-UnderlineNav-UnderlineWrapper-GWONT LocalNavigation-module__LocalNavigation__b0Xc0" aria-label="Repository" data-variant="inset" data-overflow-mode="wrap" data-hide-icons-breakpoint="medium"><ul class="prc-UnderlineNav-ItemsList-oj8gN prc-components-UnderlineItemList-xKlKC" role="list"><li role="presentation" aria-hidden="true" class="prc-UnderlineNav-WrapSpacer--aLgz"></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a aria-current="page" data-tab-item="code" data-react-nav="code-view" data-react-nav-anchor="code-view-repo-link" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg></span><span data-component="text" data-content="Code">Code</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="issues" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph/issues" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg></span><span data-component="text" data-content="Issues">Issues</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="pull-requests" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph/pulls" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-pull-request" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"></path></svg></span><span data-component="text" data-content="Pull requests">Pull requests</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="actions" data-react-nav="actions-workflows" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph/actions" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-play" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"></path></svg></span><span data-component="text" data-content="Actions">Actions</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="projects" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph/projects" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-table" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM6.5 6.5v8h7.75a.25.25 0 0 0 .25-.25V6.5Zm8-1.5V1.75a.25.25 0 0 0-.25-.25H6.5V5Zm-13 1.5v7.75c0 .138.112.25.25.25H5v-8ZM5 5V1.5H1.75a.25.25 0 0 0-.25.25V5Z"></path></svg></span><span data-component="text" data-content="Projects">Projects</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="security-and-quality" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph/security" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.467.133a1.748 1.748 0 0 1 1.066 0l5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667Zm.61 1.429a.25.25 0 0 0-.153 0l-5.25 1.68a.25.25 0 0 0-.174.238V7c0 1.358.275 2.666 1.057 3.86.784 1.194 2.121 2.34 4.366 3.297a.196.196 0 0 0 .154 0c2.245-.956 3.582-2.104 4.366-3.298C13.225 9.666 13.5 8.36 13.5 7V3.48a.251.251 0 0 0-.174-.237l-5.25-1.68ZM8.75 4.75v3a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 1.5 0ZM9 10.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg></span><span data-component="text" data-content="Security and quality">Security and quality</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="insights" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/eSlider/2dph/pulse" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-graph" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg></span><span data-component="text" data-content="Insights">Insights</span></a></li></ul><div class="prc-UnderlineNav-MoreButtonContainer-Dnrq6"><div class="prc-UnderlineNav-MoreButtonDivider-dN0a-"></div><button data-component="overflow-menu-button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk prc-UnderlineNav-MoreButton-Y8soj" data-loading="false" data-size="medium" data-variant="invisible" id="_R_2ktl_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3"><span>More<span class="prc-src-InternalVisuallyHidden-2YaI6"> items</span></span></span></span><span data-component="trailingAction" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></button></div></nav><div class="d-none"></div></header></div>
</react-partial>


  



<turbo-frame id="repo-content-turbo-frame" target="_top" data-turbo-action="advance" class="">
    <div id="repo-content-pjax-container" class="repository-content " >
    



    
      
    








<react-app
  app-name="code-view"
  initial-path="/eSlider/2dph/blob/main/PLAN.md"
  style="display: block; min-height: calc(100vh - 64px);"
  data-attempted-ssr="true"
  data-ssr="true"
  data-lazy="false"
  data-alternate="false"
  data-data-router-enabled="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-app.embeddedData">{"payload":{"codeViewBlobRoute":{"csv":null,"csvError":null,"headerInfo":{"toc":[{"level":1,"text":"PLAN — 2dph (deductionphile)","anchor":"plan--2dph-deductionphile","htmlText":"PLAN — 2dph (deductionphile)"},{"level":2,"text":"What","anchor":"what","htmlText":"What"},{"level":2,"text":"Decisions (recorded)","anchor":"decisions-recorded","htmlText":"Decisions (recorded)"},{"level":2,"text":"Architecture","anchor":"architecture","htmlText":"Architecture"},{"level":2,"text":"Schema (first pass)","anchor":"schema-first-pass","htmlText":"Schema (first pass)"},{"level":2,"text":"Config","anchor":"config","htmlText":"Config"},{"level":2,"text":"Tooling conventions","anchor":"tooling-conventions","htmlText":"Tooling conventions"},{"level":2,"text":"Open questions (v2)","anchor":"open-questions-v2","htmlText":"Open questions (v2)"},{"level":2,"text":"Mail pipeline (done)","anchor":"mail-pipeline-done","htmlText":"Mail pipeline (done)"},{"level":2,"text":"CI/CD pipeline (D15)","anchor":"cicd-pipeline-d15","htmlText":"CI/CD pipeline (D15)"},{"level":2,"text":"Execution order","anchor":"execution-order","htmlText":"Execution order"},{"level":2,"text":"Gap to v1 (epic #16)","anchor":"gap-to-v1-epic-16","htmlText":"Gap to v1 (epic #16)"},{"level":2,"text":"2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)","anchor":"2026-09-03--l-93-формальные-проверки-url-identitycontradictionexcluded-middlesufficient-reason-gitea-232-epic-229","htmlText":"2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)"},{"level":2,"text":"2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)","anchor":"2026-09-02--l-94-git-commits--commitpersonauthored-граф-gitea-233-epic-229","htmlText":"2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)"},{"level":2,"text":"2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)","anchor":"2026-09-02--l-95-сеть-связей-кто-с-кем-и-через-кого-gitea-234-epic-229","htmlText":"2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)"},{"level":2,"text":"2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)","anchor":"2026-09-02--d-12-messageperson-граф-схема--write-путь-gitea-259-epic-257","htmlText":"2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)"},{"level":2,"text":"2026-08-18 — Go-only write path sync (gitea #41)","anchor":"2026-08-18--go-only-write-path-sync-gitea-41","htmlText":"2026-08-18 — Go-only write path sync (gitea #41)"},{"level":2,"text":"2026-08-22 — Typed config (gitea #90, epic #88)","anchor":"2026-08-22--typed-config-gitea-90-epic-88","htmlText":"2026-08-22 — Typed config (gitea #90, epic #88)"},{"level":2,"text":"2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)","anchor":"2026-08-22--mime-parser-swap-in-mailconv-gitea-95-epic-88","htmlText":"2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)"},{"level":2,"text":"2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)","anchor":"2026-08-22--mailsync-v1-мелкие-фиксы-gitea-111-epic-88","htmlText":"2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)"},{"level":2,"text":"2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)","anchor":"2026-08-23--pr--merge-featimap-mailsync-v1--releasev1-gitea-112-epic-88","htmlText":"2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)"},{"level":2,"text":"2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)","anchor":"2026-08-23--reasoner-client--api-client-canon-gitea-93-epic-88","htmlText":"2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)"},{"level":2,"text":"2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)","anchor":"2026-08-22--go-config-wave-2-tools--tests-gitea-91-epic-88","htmlText":"2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)"},{"level":2,"text":"2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)","anchor":"2026-08-22--pkgutils--static-typing-hotspots-gitea-92-epic-88","htmlText":"2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)"},{"level":2,"text":"2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)","anchor":"2026-08-22--ladybug-write-path-hardening--soak-gitea-109-epic-88","htmlText":"2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)"},{"level":2,"text":"2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)","anchor":"2026-08-23--etl-реестр-обработчиков--безопасный-walker-gitea-96-epic-88","htmlText":"2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)"},{"level":2,"text":"2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)","anchor":"2026-08-25--source-adapters-gitea-97--bounded-etl-runner-gitea-98-epic-88","htmlText":"2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)"},{"level":2,"text":"2026-08-22 — singleton-cache ingest model (gitea #110)","anchor":"2026-08-22--singleton-cache-ingest-model-gitea-110","htmlText":"2026-08-22 — singleton-cache ingest model (gitea #110)"},{"level":2,"text":"2026-09-23 — shared embedding model on the search path (gitea #335)","anchor":"2026-09-23--shared-embedding-model-on-the-search-path-gitea-335","htmlText":"2026-09-23 — shared embedding model on the search path (gitea #335)"},{"level":2,"text":"2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)","anchor":"2026-08-23--concurrency-audit-mailsync-corpuswatch-index-bench-gitea-94","htmlText":"2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)"},{"level":2,"text":"2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)","anchor":"2026-08-23--url-адресация-контента-селекторы-гранулярный-сплит-gitea-100","htmlText":"2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)"},{"level":2,"text":"2026-08-23 — secret-scan в CI всех репо (gitea #142)","anchor":"2026-08-23--secret-scan-в-ci-всех-репо-gitea-142","htmlText":"2026-08-23 — secret-scan в CI всех репо (gitea #142)"},{"level":2,"text":"2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)","anchor":"2026-08-23--conversation-канон-модель--диск--манифест--граф-gitea-99-epic-88","htmlText":"2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)"},{"level":2,"text":"2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)","anchor":"2026-08-25--oo-mail-html-шаблон--render-check-до-отправки-gitea-76","htmlText":"2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)"},{"level":2,"text":"2026-08-25 — CRM association two-source rule (gitea #55, epic #52)","anchor":"2026-08-25--crm-association-two-source-rule-gitea-55-epic-52","htmlText":"2026-08-25 — CRM association two-source rule (gitea #55, epic #52)"},{"level":2,"text":"2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)","anchor":"2026-08-25--facts-lookup--contradiction-as-not-confirmed-test-plan-gitea-58-epic-52","htmlText":"2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)"},{"level":2,"text":"2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)","anchor":"2026-08-25--facts-root-ingestion-repair-empty-facts-layer-gitea-181","htmlText":"2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)"},{"level":2,"text":"2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)","anchor":"2026-08-25--импорт-гэп-почтыконтактов-с-mnt8tb-gitea-79","htmlText":"2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)"},{"level":2,"text":"2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)","anchor":"2026-08-25--docsskills-синк-под-текущий-тулзверь-агентов-gitea-81","htmlText":"2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)"},{"level":2,"text":"2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)","anchor":"2026-08-25--hnsw-leaf_vec-краш--in-process-cosine-scan-gitea-192","htmlText":"2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)"},{"level":2,"text":"2026-08-26 — весь mail-корпус в brain (gitea #199)","anchor":"2026-08-26--весь-mail-корпус-в-brain-gitea-199","htmlText":"2026-08-26 — весь mail-корпус в brain (gitea #199)"},{"level":2,"text":"2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)","anchor":"2026-08-25--импорт-outlook-pst-readpst--e--eml--пайплайн-gitea-185","htmlText":"2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)"},{"level":2,"text":"2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)","anchor":"2026-09-02--d-13-импорт-gator-wheregroup--граф-gitea-260-epic-257","htmlText":"2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)"},{"level":2,"text":"2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)","anchor":"2026-09-03--n-11-фильтр-людикомпании-vs-сервис-аккаунты-для-accept-сетей-gitea-268-epic-267","htmlText":"2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)"},{"level":2,"text":"2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)","anchor":"2026-09-03--n-12-коннектор-oo-импорт-accept-манифеста-сети-в-crm-gitea-269-epic-267","htmlText":"2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)"},{"level":2,"text":"2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)","anchor":"2026-09-03--n-14-компания-группировка-по-домену--дописывание-тегаabout-matched-gitea-271-epic-267","htmlText":"2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)"},{"level":2,"text":"2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)","anchor":"2026-09-03--p-95-сервисные-клиенты-поверх-read-контракта-gitea-241-epic-239","htmlText":"2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)"}]},"issueTemplate":null,"discussionTemplate":null,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003ePLAN — 2dph (deductionphile)\u003c/h1\u003e\u003ca id=\"user-content-plan--2dph-deductionphile\" class=\"anchor\" aria-label=\"Permalink: PLAN — 2dph (deductionphile)\" href=\"#plan--2dph-deductionphile\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eA brain that loves facts and deduction. Evidence-first knowledge graph + hybrid\nRAG over the operational Brain/ops/eSlider stack. Built like Sherlock\nHolmes: nothing is asserted unless it has proof.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eStatus: \u003cstrong\u003ev1 in\u003c/strong\u003e (epic \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/16\" rel=\"nofollow\"\u003e#16\u003c/a\u003e closed).\nv2 board: milestone \u003ca href=\"https://git.produktor.io/eSlider/2dph/milestone/13\" rel=\"nofollow\"\u003ev2\u003c/a\u003e —\nOCR \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/6\" rel=\"nofollow\"\u003e#6\u003c/a\u003e in,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/29\" rel=\"nofollow\"\u003e#29\u003c/a\u003e OQ1 in,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/30\" rel=\"nofollow\"\u003e#30\u003c/a\u003e OQ3 in,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/34\" rel=\"nofollow\"\u003e#34\u003c/a\u003e D23 in,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/36\" rel=\"nofollow\"\u003e#36\u003c/a\u003e OQ5/D24 in,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/102\" rel=\"nofollow\"\u003e#102\u003c/a\u003e gs PDF normalize in,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/163\" rel=\"nofollow\"\u003e#163\u003c/a\u003e browser-sync in\n(\u003ccode\u003ebin/cron/browser-sync.go\u003c/code\u003e + 6-hourly systemd timer, reads\n\u003ccode\u003evar/corpus/{gmail,linkedin,djinni}\u003c/code\u003e → POST /ingest; Thorium down tolerated),\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/82\" rel=\"nofollow\"\u003e#82\u003c/a\u003e Synapse Matrix (leafs+edges\nas a service for pc-agent) in — \u003ccode\u003ebin/brain/synapse-matrix.go\u003c/code\u003e, docs\n\u003ca href=\"/eSlider/2dph/blob/main/docs/brain-synapse.md\"\u003edocs/brain-synapse.md\u003c/a\u003e.\nGap: \u003ca href=\"/eSlider/2dph/blob/main/docs/roadmap.md\"\u003edocs/roadmap.md\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eWhat\u003c/h2\u003e\u003ca id=\"user-content-what\" class=\"anchor\" aria-label=\"Permalink: What\" href=\"#what\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eA single embedded store (LadybugDB, one \u003ccode\u003e.lbug\u003c/code\u003e file) that holds:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003ea \u003cstrong\u003eproperty graph\u003c/strong\u003e (Cypher) over the ops corpus and portfolio,\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFTS5\u003c/strong\u003e (BM25) and \u003cstrong\u003eHNSW vector\u003c/strong\u003e indexes (model2vec embeddings),\u003c/li\u003e\n\u003cli\u003eevery node/edge annotated with \u003ccode\u003eroot\u003c/code\u003e (\u003ccode\u003efacts\u003c/code\u003e | \u003ccode\u003einfo\u003c/code\u003e), \u003ccode\u003econfidence\u003c/code\u003e\n(\u003ccode\u003econfirmed\u003c/code\u003e | \u003ccode\u003epartial\u003c/code\u003e | \u003ccode\u003ehypothesis\u003c/code\u003e), \u003ccode\u003eevidence[]\u003c/code\u003e, \u003ccode\u003ehow\u003c/code\u003e, \u003ccode\u003ewhere\u003c/code\u003e, \u003ccode\u003ewhen\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003eSearch = \u003cstrong\u003ededuction\u003c/strong\u003e: facts root first, info root second, \u003ccode\u003eweb-search\u003c/code\u003e as the\nsecond independent source when local roots cannot confirm. Following the\ndetective method: \u003cstrong\u003ea fact needs ≥2 independent sources or it is\n\u003ccode\u003e(not confirmed)\u003c/code\u003e.\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eDecisions (recorded)\u003c/h2\u003e\u003ca id=\"user-content-decisions-recorded\" class=\"anchor\" aria-label=\"Permalink: Decisions (recorded)\" href=\"#decisions-recorded\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e#\u003c/th\u003e\n\u003cth\u003eQuestion\u003c/th\u003e\n\u003cth\u003eAnswer\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eD1\u003c/td\u003e\n\u003ctd\u003eRAG corpus\u003c/td\u003e\n\u003ctd\u003eops stack (chat, onlyoffice, gitea/NPM, searchxng, observability, ai-bot, mcp-servers, \u003ccode\u003e~/.ssh/config\u003c/code\u003e) + portfolio. Exclude \u003ccode\u003eoffice.dev\u003c/code\u003e + jobs/applications.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD2\u003c/td\u003e\n\u003ctd\u003eskill merging\u003c/td\u003e\n\u003ctd\u003eintegrate skills \u003cstrong\u003ein this project\u003c/strong\u003e \u003ccode\u003eskills/\u003c/code\u003e; skip gitea / brain-dependent skills.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD3\u003c/td\u003e\n\u003ctd\u003eweb search\u003c/td\u003e\n\u003ctd\u003eGo client \u003ccode\u003ebin/web/search.go\u003c/code\u003e (\u003ccode\u003einternal/websearch\u003c/code\u003e). SearXNG URL is config (\u003ccode\u003eBRAIN_SEARCH_URL\u003c/code\u003e). Optional Compose profile \u003ccode\u003esearxng\u003c/code\u003e (sanitized settings). Do not run a second copy on a host that already has one. Empty/\u003ccode\u003ethrottled\u003c/code\u003e ≠ “nothing exists”.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD4\u003c/td\u003e\n\u003ctd\u003eembeddings\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003emodel2vec\u003c/strong\u003e \u003ccode\u003eminishlab/potion-multilingual-128M\u003c/code\u003e instead of embeddinggemma.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD5\u003c/td\u003e\n\u003ctd\u003eparser\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003emistune\u003c/strong\u003e for MD → leaf extraction (duckdb-md documented as future optional SQL/export layer, not v1).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD6\u003c/td\u003e\n\u003ctd\u003egraph engine\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003eLadybugDB\u003c/strong\u003e. Go is the service (\u003ccode\u003ebin/brain/search.go\u003c/code\u003e, \u003ccode\u003ebin/brain/serve.go\u003c/code\u003e in-process, \u003ccode\u003einternal/brain\u003c/code\u003e). Read path is Go + Zig CGO (D21). Python \u003ccode\u003ebin/kb/{get,stats,eval}\u003c/code\u003e is the CI fallback when Zig/libs are not fetched. Incremental write is Python \u003ccode\u003ebin/kb/add\u003c/code\u003e (\u003ccode\u003ebin/brain/add.go\u003c/code\u003e). Bulk rebuild stays \u003ccode\u003ecompose --profile index\u003c/code\u003e until the Go write path is safe.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD7\u003c/td\u003e\n\u003ctd\u003edb access\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003edb-yaml\u003c/code\u003e/\u003ccode\u003epsql-yq\u003c/code\u003e-style, read-only, YAML out. OnlyOffice Postgres via SSH tunnel (\u003ccode\u003e127.0.0.1:5433\u003c/code\u003e).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD8\u003c/td\u003e\n\u003ctd\u003eevidence\u003c/td\u003e\n\u003ctd\u003edetective method: ≥2 independent sources or \u003ccode\u003e(not confirmed)\u003c/code\u003e. 2-source auto-pair docker ps × compose × ssh-config × docs.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD9\u003c/td\u003e\n\u003ctd\u003efacts/goal model\u003c/td\u003e\n\u003ctd\u003eWho / What / How / Where / When + evidence + confidence on every edge.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD10\u003c/td\u003e\n\u003ctd\u003eversioning\u003c/td\u003e\n\u003ctd\u003eeverything is a leaf with \u003ccode\u003esha256 + observed_at + source_rev\u003c/code\u003e; \u003ccode\u003eFile-[:HAS_VERSION]-\u0026gt;Commit-[:AUTHORED]-\u0026gt;Person\u003c/code\u003e. Stale = \u003ccode\u003esource_rev\u003c/code\u003e \u0026lt; git HEAD.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD11\u003c/td\u003e\n\u003ctd\u003estrong/weak\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003eroot\u003c/code\u003e column: \u003ccode\u003efacts\u003c/code\u003e (strong) vs \u003ccode\u003einfo\u003c/code\u003e (weak). Answer is \u003ccode\u003econfirmed\u003c/code\u003e only from facts root.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD12\u003c/td\u003e\n\u003ctd\u003etransactional\u003c/td\u003e\n\u003ctd\u003efacts and info split by root but \u003cstrong\u003ewritten in the same Ladybug transaction (ACID)\u003c/strong\u003e on every write.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD13\u003c/td\u003e\n\u003ctd\u003eportfolio\u003c/td\u003e\n\u003ctd\u003estart graph \u003ccode\u003e(Person:eslider)-[:HAS]-\u0026gt;(Portfolio)\u003c/code\u003e, associate other natural/juristic persons later.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD14\u003c/td\u003e\n\u003ctd\u003etooling style\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ebin/{subject}/{method}.go\u003c/code\u003e shebang (e.g. \u003ccode\u003ebin/brain/search.go\u003c/code\u003e). Shared code in \u003ccode\u003einternal/\u003c/code\u003e. One root \u003ccode\u003ego.mod\u003c/code\u003e + \u003ccode\u003ego.work\u003c/code\u003e. No \u003ccode\u003ebin/*/main.go\u003c/code\u003e, no nested modules.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD15\u003c/td\u003e\n\u003ctd\u003erepo\u003c/td\u003e\n\u003ctd\u003eGitea \u003ca href=\"https://git.produktor.io/eSlider/2dph\" rel=\"nofollow\"\u003e\u003ccode\u003eeSlider/2dph\u003c/code\u003e\u003c/a\u003e is origin + \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues\" rel=\"nofollow\"\u003eissues\u003c/a\u003e. GitHub \u003ccode\u003eeSlider/2dph\u003c/code\u003e is the public clone (PRs + Actions CI). No direct \u003ccode\u003emain\u003c/code\u003e pushes. TDD → PR → CI green → merge.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD16\u003c/td\u003e\n\u003ctd\u003econtradictions\u003c/td\u003e\n\u003ctd\u003e≥2 yes vs ≥2 no → hypothesis → \u003ccode\u003e(not confirmed)\u003c/code\u003e until a rule fires. Order: \u003cstrong\u003etemporal_freshness\u003c/strong\u003e (fresh ≥2 vs stale minority), then \u003cstrong\u003eauthority_pairing\u003c/strong\u003e (runtime/config A×B beats narrative C). Store as \u003ccode\u003ea x b vs c x d\u003c/code\u003e on hypothesis leafs. \u003ccode\u003ebin/facts/audit contradict\u003c/code\u003e. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/29\" rel=\"nofollow\"\u003e#29\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD17\u003c/td\u003e\n\u003ctd\u003eassertion gate\u003c/td\u003e\n\u003ctd\u003eFact-check every \u003cem\u003eclaim\u003c/em\u003e (facts → info → live → web), not every edit. \u003ccode\u003ebin/brain/search.go\u003c/code\u003e adds a \u003ccode\u003eweb\u003c/code\u003e block when there is no facts hit (\u003ccode\u003ethrottled\u003c/code\u003e/\u003ccode\u003eskipped\u003c/code\u003e/\u003ccode\u003erefused\u003c/code\u003e ≠ absence). \u003ccode\u003e--root\u003c/code\u003e and \u003ccode\u003e--no-web\u003c/code\u003e stay local. Missing graph ≠ “does not exist”.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD18\u003c/td\u003e\n\u003ctd\u003ereasoner\u003c/td\u003e\n\u003ctd\u003ePluggable OpenAI-compatible URL (\u003ccode\u003eREASONER_BASE_URL\u003c/code\u003e). RAM: \u003ccode\u003eQwen/Qwen3.5-9B\u003c/code\u003e. Quality: \u003ccode\u003eprism-ml/Bonsai-27B-gguf\u003c/code\u003e or \u003ccode\u003eQwen/Qwen3.6-27B\u003c/code\u003e. No official Qwen3.6-9B. CPU bake-off: \u003ccode\u003ebin/reasoner/bench.go\u003c/code\u003e + compose profile \u003ccode\u003ereasoner\u003c/code\u003e (\u003ccode\u003eOLLAMA_NUM_GPU=0\u003c/code\u003e, \u003ccode\u003e:11435\u003c/code\u003e). PicoClaw is compose profile \u003ccode\u003epicoclaw\u003c/code\u003e; tools are \u003ccode\u003esearch\u003c/code\u003e/\u003ccode\u003eget\u003c/code\u003e/\u003ccode\u003eaudit\u003c/code\u003e. Weights are not copied into the 2dph image. Agent lever/loop: \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/15\" rel=\"nofollow\"\u003e#15\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD19\u003c/td\u003e\n\u003ctd\u003egit history\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/go-git/go-git\"\u003ego-git\u003c/a\u003e via \u003ccode\u003ebin/brain/import-git.go\u003c/code\u003e (lib \u003ccode\u003einternal/gitlog\u003c/code\u003e). No subprocess of the git binary. Reads history and upserts commit leafs into the brain directly (\u003ccode\u003e--dry-run\u003c/code\u003e to preview).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD20\u003c/td\u003e\n\u003ctd\u003eagent API\u003c/td\u003e\n\u003ctd\u003eOpenAPI + MCP are generated from the same \u003ccode\u003epkg/httpapi.Ops\u003c/code\u003e table as \u003ccode\u003ebin/brain/serve.go\u003c/code\u003e handlers. \u003ccode\u003eGET /openapi.json\u003c/code\u003e, \u003ccode\u003ePOST /mcp\u003c/code\u003e (JSON-RPC tools/list + tools/call). Tool names match OpenAPI paths (\u003ccode\u003esearch\u003c/code\u003e/\u003ccode\u003eget\u003c/code\u003e/\u003ccode\u003estats\u003c/code\u003e/\u003ccode\u003eaudit\u003c/code\u003e/\u003ccode\u003eingest\u003c/code\u003e).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD21\u003c/td\u003e\n\u003ctd\u003eCGO\u003c/td\u003e\n\u003ctd\u003eLadybug/tokenizers CGO is compiled with \u003cstrong\u003eZig\u003c/strong\u003e (\u003ccode\u003ebin/cgo/zcc\u003c/code\u003e → \u003ccode\u003ezig cc -target …-linux-gnu\u003c/code\u003e), not gcc. \u003ccode\u003ebin/cgo/zig\u003c/code\u003e pins Zig 0.14.1 + liblbug 0.19.1 + libtokenizers 1.27.0. Compose \u003ccode\u003etarget: api\u003c/code\u003e has no CPython; write/rebuild is profile \u003ccode\u003eindex\u003c/code\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD22\u003c/td\u003e\n\u003ctd\u003eanalytics\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003educkdb-go\u003c/strong\u003e in-process (\u003ccode\u003epkg/duckdb\u003c/code\u003e, \u003ccode\u003ebin/jsonl/stats.go\u003c/code\u003e) for quantiles/JSONL. Links with \u003cstrong\u003egcc/g++\u003c/strong\u003e, not Zig. Ladybug stays the graph; web-search cache stays modernc sqlite. Slice small structured docs with \u003cstrong\u003emikefarah/yq\u003c/strong\u003e, not kislyuk/jq. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/30\" rel=\"nofollow\"\u003e#30\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD23\u003c/td\u003e\n\u003ctd\u003eCLI\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003eflaggy\u003c/strong\u003e (\u003ccode\u003egithub.com/integrii/flaggy\u003c/code\u003e, 0 deps). Flags at any position. Wrapper \u003ccode\u003epkg/cli\u003c/code\u003e. Bash complete: \u003ccode\u003esource \u0026lt;(./bin/shell/complete.go bash)\u003c/code\u003e. No cobra, no stdlib \u003ccode\u003eflag\u003c/code\u003e in Go tools. Search does not intercept the word \u003ccode\u003ecompletion\u003c/code\u003e. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/34\" rel=\"nofollow\"\u003e#34\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD24\u003c/td\u003e\n\u003ctd\u003efact intervals\u003c/td\u003e\n\u003ctd\u003eLeaf \u003ccode\u003evalid_from\u003c/code\u003e / \u003ccode\u003evalid_to\u003c/code\u003e (YYYY-MM-DD, inclusive; empty = open/legacy). Search \u003ccode\u003e--as-of\u003c/code\u003e / MCP \u003ccode\u003eas_of\u003c/code\u003e keeps facts active that day. Not D16 \u003ccode\u003etemporal_freshness\u003c/code\u003e (source stale vs HEAD). Empty interval = always visible. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/36\" rel=\"nofollow\"\u003e#36\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD25\u003c/td\u003e\n\u003ctd\u003edeploy data path\u003c/td\u003e\n\u003ctd\u003eBrain serves from host \u003ccode\u003evar/\u003c/code\u003e, not named volumes. Compose binds \u003ccode\u003e./var:/data/var\u003c/code\u003e (kb.lbug at \u003ccode\u003e/data/var/kb.lbug\u003c/code\u003e), HF model from \u003ccode\u003evar/hf\u003c/code\u003e, Ladybug FTS/VECTOR extensions mounted read-only into \u003ccode\u003e$HOME/.lbdb/extension\u003c/code\u003e. \u003ccode\u003ebrain\u003c/code\u003e uses \u003ccode\u003enetwork_mode: host\u003c/code\u003e so \u003ccode\u003e127.0.0.1:8630\u003c/code\u003e works with any image (KB_HOST-independent). Named volumes \u003ccode\u003ekb-model\u003c/code\u003e/\u003ccode\u003ekb-var\u003c/code\u003e dropped — live data is host \u003ccode\u003evar/\u003c/code\u003e (gitignored).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD26\u003c/td\u003e\n\u003ctd\u003esearch A/B (#201/#202)\u003c/td\u003e\n\u003ctd\u003eНичего не «лучше» без A/B. \u003ccode\u003ebin/brain/bench.go\u003c/code\u003e + golden-set (\u003ccode\u003einternal/brain/testdata/golden-set.json\u003c/code\u003e, ~50 запросов, рус+англ, факты/mail/docs/git/ssh). Метрики: latency p50/p95/mean, recall@5/@10 (fragment-гейт как eval.go + A/B vs baseline IDs), CPU/RSS из /proc. Гейты: recall@5 ≥ 0.95, кандидат p50 ≤ baseline × 1.5. Baseline: линейный скан 313k = p50 ~31s. Кандидаты (ANN/SIMD/FTS-first) — child issues эпика #201.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD32\u003c/td\u003e\n\u003ctd\u003eANN вне liblbug (#201/#204)\u003c/td\u003e\n\u003ctd\u003eИнкрементальный векторный индекс в \u003ccode\u003einternal/brain/ann\u003c/code\u003e: \u003cstrong\u003eIVF\u003c/strong\u003e (k-means + точный косинус по probed-ячейкам, чистый Go; \u003ccode\u003ecoder/hnsw\u003c/code\u003e/HNSW отброшен — на 313k реальных достижимы 34% узлов, recall@5 0.16). Индекс \u003ccode\u003evar/state/vector.ann\u003c/code\u003e + append-only WAL (\u003ccode\u003eupsert\u003c/code\u003e = только новые leafs, без rebuild). \u003ccode\u003ebin/brain/ann.go\u003c/code\u003e (build/upsert/ensure/stats/api/search), wave-шаг \u003ccode\u003eann-build\u003c/code\u003e (#206). \u003ccode\u003equeryVector\u003c/code\u003e: индекс top-k → point-lookup метаданных → слияние с FTS (rank не тронут); fallback на скан при отсутствии/повреждении. Конфиг \u003ccode\u003evector.ann\u003c/code\u003e (NList=2000, NProbe=128 — дефолты пакета; прод-конфиг nprobe=2000, решение #206). A/B: baseline p50 ~31s → кандидат p50 ~0.4s, recall@5 vs baseline 1.0 (цифры в #204/#206).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD26\u003c/td\u003e\n\u003ctd\u003erepo layout\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003ebin/{subject}\u003c/code\u003e = tools only (\u003ccode\u003e{verb}-{object}.go\u003c/code\u003e); executables/orchestration → \u003ccode\u003escripts/\u003c/code\u003e; data tree \u003ccode\u003evar/{tmp,dist,log,state,cache,hf,kb.lbug,corpus/{git,mail,chats,docs}}\u003c/code\u003e; configs \u003ccode\u003eetc/{subject}\u003c/code\u003e + central \u003ccode\u003eetc/brain/config.yml\u003c/code\u003e. CI guard fails on absolute paths and Python remnants. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/89\" rel=\"nofollow\"\u003e#89\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD27\u003c/td\u003e\n\u003ctd\u003econfig system\u003c/td\u003e\n\u003ctd\u003eAll external sources (paths, URLs, endpoints) via \u003ccode\u003egithub.com/eslider/go-config\u003c/code\u003e (\u003ccode\u003einternal/config\u003c/code\u003e): stack \u003ccode\u003eetc/brain/config.yml\u003c/code\u003e → \u003ccode\u003econfig.local.yml\u003c/code\u003e → \u003ccode\u003e.env\u003c/code\u003e → process env; one load per process into a strict typed struct; deep merge (maps recurse, scalars last-write-wins, keys lower+alnum); no ad-hoc \u003ccode\u003eos.Getenv\u003c/code\u003e; legacy \u003ccode\u003eKB_*\u003c/code\u003e / \u003ccode\u003eBRAIN_SEARCH_*\u003c/code\u003e env names honored transitionally. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/90\" rel=\"nofollow\"\u003e#90\u003c/a\u003e, \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/91\" rel=\"nofollow\"\u003e#91\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD28\u003c/td\u003e\n\u003ctd\u003eno hardcoded paths/URLs\u003c/td\u003e\n\u003ctd\u003eNothing like \u003ccode\u003e/mnt/…\u003c/code\u003e, \u003ccode\u003e/home/\u0026lt;user\u0026gt;/…\u003c/code\u003e, or pinned host URLs in code/comments/tests; missing config key = explicit error pointing at \u003ccode\u003eetc/brain/config.yml\u003c/code\u003e. CI-enforced. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/89\" rel=\"nofollow\"\u003e#89\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD29\u003c/td\u003e\n\u003ctd\u003etyped code\u003c/td\u003e\n\u003ctd\u003eStatic typing first: no \u003ccode\u003emap[string]any\u003c/code\u003e at boundaries; loose→strict via go-viper/mapstructure/v2; primitives only in \u003ccode\u003epkg/utils/*.go\u003c/code\u003e. Hotspots listed in \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/92\" rel=\"nofollow\"\u003e#92\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD30\u003c/td\u003e\n\u003ctd\u003eowned repos\u003c/td\u003e\n\u003ctd\u003eAll \u003ccode\u003egithub.com/eSlider/go-*\u003c/code\u003e are owned: Gitea \u003ccode\u003egit.produktor.io/eSlider/*\u003c/code\u003e = working home (issues/releases only there), GitHub = push-mirror; module paths stay \u003ccode\u003egithub.com/eSlider/go-*\u003c/code\u003e. Shared template: subpackage-per-domain. Gitea fetch via \u003ccode\u003ereplace\u003c/code\u003e in \u003ccode\u003ego.mod\u003c/code\u003e: \u003ccode\u003ego-config@v0.4.0\u003c/code\u003e, \u003ccode\u003ego-onlyoffice@v0.14.0\u003c/code\u003e → \u003ccode\u003egit.produktor.io/eSlider/*\u003c/code\u003e (go.sum carries Gitea hashes only; zero \u003ccode\u003egithub.com/eSlider/go-*\u003c/code\u003e downloads). \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/101\" rel=\"nofollow\"\u003e#101\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD31\u003c/td\u003e\n\u003ctd\u003econcurrency\u003c/td\u003e\n\u003ctd\u003ePublic APIs synchronous; ctx-first IO; bounded pools; wg-accounted goroutines; sender closes channels; errgroup for fan-out/fan-in; backpressure over unbounded queues; graceful shutdown; \u003ccode\u003ego test -race\u003c/code\u003e in CI. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/94\" rel=\"nofollow\"\u003e#94\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD32\u003c/td\u003e\n\u003ctd\u003ezero-alloc hot paths\u003c/td\u003e\n\u003ctd\u003ePre-allocation, buffer reuse (\u003ccode\u003ebuf[:0]\u003c/code\u003e), \u003ccode\u003esync.Pool\u003c/code\u003e with reset, \u003ccode\u003estrings.Builder\u003c/code\u003e over \u003ccode\u003e+\u003c/code\u003e, sets as \u003ccode\u003emap[K]struct{}\u003c/code\u003e, structs over maps; proven by \u003ccode\u003e-benchmem\u003c/code\u003e. Applies to hot paths only — no premature optimization.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD33\u003c/td\u003e\n\u003ctd\u003esync-ETL reimplementation\u003c/td\u003e\n\u003ctd\u003ePipeline \u003ccode\u003eSource.Fetch(ctx,cursor)→[]Blob→Registry.Decode→Transform→Load\u003c/code\u003e; one Handler per format (eml=emersion/go-message \u003ccode\u003eWalk()\u003c/code\u003e, zip=\u003ccode\u003earchive/zip\u003c/code\u003e, html=\u003ccode\u003ex/net/html\u003c/code\u003e optional, pdf=Ghostscript normalize→pdftotext/tesseract); lazy children, walker safety limits; single implementation per transformer; canonical \u003ccode\u003eConversation\u003c/code\u003e model on disk (\u003ccode\u003evar/corpus\u003c/code\u003e) → brain graph (\u003ccode\u003eSENT/TO/CC/BCC/REPLY_TO/PART_OF\u003c/code\u003e); URL addressing \u003ccode\u003escheme://platform/path[#anchor]\u003c/code\u003e with \u003ccode\u003esha256(URL)[:16]\u003c/code\u003e node IDs. Epic \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/88\" rel=\"nofollow\"\u003e#88\u003c/a\u003e: \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/95\" rel=\"nofollow\"\u003e#95\u003c/a\u003e–\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/102\" rel=\"nofollow\"\u003e#102\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD34\u003c/td\u003e\n\u003ctd\u003esynapse edges\u003c/td\u003e\n\u003ctd\u003eUser-defined edges between neurons modeled as a \u003ccode\u003eSYNAPTIC (FROM Leaf TO Leaf, type STRING)\u003c/code\u003e rel table (leafs = neurons, edges = synapses); adjacency = both directions; path = \u003ccode\u003e[:SYNAPTIC* ALL SHORTEST 1..N]\u003c/code\u003e (Ladybug exposes only intermediate nodes → rebuild \u003ccode\u003e[from, mid…, to]\u003c/code\u003e). Service \u003ccode\u003ebin/brain/synapse-matrix.go\u003c/code\u003e, auth = token OR loopback bind, config section \u003ccode\u003esynapse\u003c/code\u003e. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/82\" rel=\"nofollow\"\u003e#82\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD35\u003c/td\u003e\n\u003ctd\u003eANN в проде (#206)\u003c/td\u003e\n\u003ctd\u003eПобедитель A/B (#204) включён по умолчанию: \u003ccode\u003evector.ann.enabled: true\u003c/code\u003e (serve + CLI), \u003cstrong\u003enprobe=2000\u003c/strong\u003e — замерено на живом корпусе 313689 векторов (11 запросов golden-set): p50 471ms recall@5=1.0 vs 256 (p50 262ms recall@5 0.818) — корректность сначала, тюнинг позже. Волна: шаг \u003ccode\u003eann-build\u003c/code\u003e = \u003ccode\u003eann.go ensure\u003c/code\u003e — полный build только когда индекса нет/устарел (\u0026gt;10% отставания по счётчику leafs), иначе инкрементальный WAL-upsert (+100 \u0026lt;1s, никогда rebuild на волну). \u003ccode\u003eserve\u003c/code\u003e — тёплый старт: индекс грузится при старте (WarmANN), при пустом/битом — fallback на скан, build делает волна (single-writer, #204). A/B прод: p50 471ms vs baseline 33.6s (0.014x), recall@5 vs baseline 1.0.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eD36\u003c/td\u003e\n\u003ctd\u003eFTS-фаза rebuild (#244)\u003c/td\u003e\n\u003ctd\u003eПосле write-фазы \u003ccode\u003ebrain-index\u003c/code\u003e закрывает хэндл и переоткрывает БД с автоподобранным пулом \u003ccode\u003emax(1GB, chars×32)\u003c/code\u003e для \u003ccode\u003eCREATE_FTS_INDEX\u003c/code\u003e: на полном корпусе (280MB текста) 1GB-пул падает и оставляет орфан \u003ccode\u003e0_id_appears_info\u003c/code\u003e, недостижимый через SQL (внутренний каталог). Замеры порога: 10.3MB→64/128MB, 10.8MB→192/256MB, прод 280MB→1GB/10GB. Двухфазный workaround из rebuild.md автоматизирован (отменён как ручной). C-потолок (замер #244): write-фаза ~140KB/leaf линейно, пул 1GB не ограничивает, CHECKPOINT/embedding не влияют, close не освобождает в том же процессе; FTS — пул ~9GB на полном корпусе. Критерий #237 (RSS ≤2-4GB) переформулирован: Go-часть — да; C-сторона — задокументированный потолок (~14-15GB write + ~9GB FTS на 105k).\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eArchitecture\u003c/h2\u003e\u003ca id=\"user-content-architecture\" class=\"anchor\" aria-label=\"Permalink: Architecture\" href=\"#architecture\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"2dph/\n  PLAN.md / AGENTS.md\n  docs/                     published docs (this conversation → docs/ as md)\n  skills/                   in-project skills (web-search, postgres, brain, picoclaw, diataxis-docs)\n  bin/\n    brain/search.go         deduction: facts → info → web\n    brain/index.go          rebuild FTS + HNSW (incl. --with-mail)\n    brain/add.go            incremental leaf write (no rebuild)\n    brain/serve.go          HTTP API in-process + OpenAPI/MCP (D20); Zig CGO (D21)\n    brain/get.go stats.go eval.go watch.go model.go   # Go read/ops (cgo)\n    facts/extract.go audit.go crm.go  # 2-source pairing, confidence, CRM proof\n    mail/sync.go import.go convert-mbox.go ocr.go    # mail ETL (Gmail/OO/M365), PDF OCR\n    mail/incubator.go       # legacy .eml → doveadm save в mail-server-инкубатор (#252/эпик #250)\n    chats/sync.go import.go facts.go apply.go  # conversations (no chats index)\n    contacts/*.go           CRM contacts importer\n    web/search.go           SearXNG client (throttled ≠ absence)\n    git/import.go           go-git history (no git binary; conversion only)\n    markdown/import.go      H2 leaf split (Go)\n    postgres/query.go       read-only YAML (wraps scripts/db/psql-yq)\n    reasoner/bench.go        CPU tool-call bench (D18; OpenAI tools)\n    qa/stats.go             DuckDB quantiles / JSONL (D22; not the test taxonomy)\n    semver/next.go          next semver from conventional commits (Release job)\n    cli/complete.go         flaggy bash/zsh/fish complete (D23)\n    cgo/zig zcc zc++        CGO toolchain (zig cc, not gcc)\n    stack/start start-assistant start-mail-sync stop status\n    db/psql-yq              (vendored)\n    ssh-tunnel              onlyoffice pg tunnel 5433\n  test/\n    system/                 offline-gated system tests (CI default)\n    stress/                 live-brain load generator\n    integration/            opt-in live-dependency tests\n    README.md               how to run each tier\n  etc/\n    searxng/ picoclaw/      operator config (FHS; moved from deploy/)\n  var/kb.lbug               single embedded store (gitignored)\"\u003e\u003cpre class=\"notranslate\"\u003e\u003ccode\u003e2dph/\n  PLAN.md / AGENTS.md\n  docs/                     published docs (this conversation → docs/ as md)\n  skills/                   in-project skills (web-search, postgres, brain, picoclaw, diataxis-docs)\n  bin/\n    brain/search.go         deduction: facts → info → web\n    brain/index.go          rebuild FTS + HNSW (incl. --with-mail)\n    brain/add.go            incremental leaf write (no rebuild)\n    brain/serve.go          HTTP API in-process + OpenAPI/MCP (D20); Zig CGO (D21)\n    brain/get.go stats.go eval.go watch.go model.go   # Go read/ops (cgo)\n    facts/extract.go audit.go crm.go  # 2-source pairing, confidence, CRM proof\n    mail/sync.go import.go convert-mbox.go ocr.go    # mail ETL (Gmail/OO/M365), PDF OCR\n    mail/incubator.go       # legacy .eml → doveadm save в mail-server-инкубатор (#252/эпик #250)\n    chats/sync.go import.go facts.go apply.go  # conversations (no chats index)\n    contacts/*.go           CRM contacts importer\n    web/search.go           SearXNG client (throttled ≠ absence)\n    git/import.go           go-git history (no git binary; conversion only)\n    markdown/import.go      H2 leaf split (Go)\n    postgres/query.go       read-only YAML (wraps scripts/db/psql-yq)\n    reasoner/bench.go        CPU tool-call bench (D18; OpenAI tools)\n    qa/stats.go             DuckDB quantiles / JSONL (D22; not the test taxonomy)\n    semver/next.go          next semver from conventional commits (Release job)\n    cli/complete.go         flaggy bash/zsh/fish complete (D23)\n    cgo/zig zcc zc++        CGO toolchain (zig cc, not gcc)\n    stack/start start-assistant start-mail-sync stop status\n    db/psql-yq              (vendored)\n    ssh-tunnel              onlyoffice pg tunnel 5433\n  test/\n    system/                 offline-gated system tests (CI default)\n    stress/                 live-brain load generator\n    integration/            opt-in live-dependency tests\n    README.md               how to run each tier\n  etc/\n    searxng/ picoclaw/      operator config (FHS; moved from deploy/)\n  var/kb.lbug               single embedded store (gitignored)\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSchema (first pass)\u003c/h2\u003e\u003ca id=\"user-content-schema-first-pass\" class=\"anchor\" aria-label=\"Permalink: Schema (first pass)\" href=\"#schema-first-pass\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eNode tables: \u003ccode\u003ePerson, Service, Host, Container, Repo, File, Commit, Leaf\u003c/code\u003e.\n\u003ccode\u003eLeaf(embedding FLOAT[N])\u003c/code\u003e — FTS on \u003ccode\u003etext\u003c/code\u003e. Векторный поиск: ANN IVF вне\nliblbug (\u003ccode\u003einternal/brain/ann\u003c/code\u003e, #204; штатный liblbug HNSW Leaf_vec крашится\nна росте графа, #192 → встроенный скан как fallback).\nEdges: \u003ccode\u003eRUNS / USES / FROM_FILE / HAS_VERSION / AUTHORED / ABOUT / ASSOCIATED / SIMILAR_0.85\u003c/code\u003e.\n\u003ccode\u003eFROM_FILE\u003c/code\u003e / \u003ccode\u003eHAS_VERSION\u003c/code\u003e / \u003ccode\u003eAUTHORED\u003c/code\u003e: \u003ccode\u003ebin/brain/search.go --hop N\u003c/code\u003e walks\nthem from each hit (1=File, 2=Commit, 3=Person). Rebuild writes\n\u003ccode\u003eLeaf-[:FROM_FILE]-\u0026gt;File\u003c/code\u003e; git import writes the rest.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eCommon props on every node/edge: \u003ccode\u003eroot\u003c/code\u003e, \u003ccode\u003econfidence\u003c/code\u003e, \u003ccode\u003eevidence[]\u003c/code\u003e, \u003ccode\u003ehow\u003c/code\u003e,\n\u003ccode\u003ewhere\u003c/code\u003e, \u003ccode\u003ewhen\u003c/code\u003e, \u003ccode\u003esource_rev\u003c/code\u003e. Leaf interval of truth (D24): \u003ccode\u003evalid_from\u003c/code\u003e,\n\u003ccode\u003evalid_to\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eConfig\u003c/h2\u003e\u003ca id=\"user-content-config\" class=\"anchor\" aria-label=\"Permalink: Config\" href=\"#config\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003e~/.config/brain/\u003c/code\u003e (0600):\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003esearch.env\u003c/code\u003e — real \u003ccode\u003eBRAIN_SEARCH_URL/USER/PASS\u003c/code\u003e from \u003ccode\u003e~/.config/ops/npm-bot.env\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edb-profiles.yml\u003c/code\u003e — real \u003ccode\u003eonlyoffice\u003c/code\u003e profile (SSH tunnel \u003ccode\u003e127.0.0.1:5433\u003c/code\u003e,\nuser \u003ccode\u003eonlyoffice\u003c/code\u003e, db \u003ccode\u003eonlyoffice\u003c/code\u003e, \u003ccode\u003enetwork: host\u003c/code\u003e,\n\u003ccode\u003epassword_env_file: ~/.config/ops/onlyoffice.env\u003c/code\u003e) + example profiles.\n\u003ccode\u003e~/.config/ops/onlyoffice.env\u003c/code\u003e is bootstrapped from the VM's\n\u003ccode\u003e/etc/onlyoffice/documentserver/local.json\u003c/code\u003e (dbUser/dbPass), never committed.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e~/.config/brain/../\u003c/code\u003e — nothing else lives in the repo.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eTooling conventions\u003c/h2\u003e\u003ca id=\"user-content-tooling-conventions\" class=\"anchor\" aria-label=\"Permalink: Tooling conventions\" href=\"#tooling-conventions\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003ebin/{subject}/{method}\u003c/code\u003e — line 2 is a usage comment (mirrors \u003ccode\u003epsql-yq\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003ebash + golang primary (Go shebang); no Python in-tree.\u003c/li\u003e\n\u003cli\u003eYAML default output, \u003ccode\u003e--json\u003c/code\u003e for machines. Slice with mikefarah/yq.\u003c/li\u003e\n\u003cli\u003eEverything that touches the network / DB is read-only, throttled, cached.\u003c/li\u003e\n\u003cli\u003eTests (TDD) gate every commit; \u003ccode\u003egh\u003c/code\u003e + CI/CD on every push.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eOpen questions (v2)\u003c/h2\u003e\u003ca id=\"user-content-open-questions-v2\" class=\"anchor\" aria-label=\"Permalink: Open questions (v2)\" href=\"#open-questions-v2\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eOQ1: \u003cstrong\u003ein\u003c/strong\u003e — D16 adjudication: \u003ccode\u003etemporal_freshness\u003c/code\u003e then \u003ccode\u003eauthority_pairing\u003c/code\u003e.\nUnresolved 2v2 stays hypothesis. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/29\" rel=\"nofollow\"\u003e#29\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eOQ2: OCR — \u003cstrong\u003ein\u003c/strong\u003e. \u003ccode\u003epdftotext -layout\u003c/code\u003e first; scans \u003ccode\u003epdftoppm\u003c/code\u003e + tesseract\n\u003ccode\u003eeng+deu\u003c/code\u003e (\u003ccode\u003ebin/mail/ocr.go\u003c/code\u003e, \u003ccode\u003einternal/ocr\u003c/code\u003e). No gocv, no gosseract CGO\n(D21 Zig owns Ladybug CGO). Optional \u003ccode\u003eOCR_ENGINE=paddle\u003c/code\u003e / compose profile\n\u003ccode\u003eocr-paddle\u003c/code\u003e. Docling left the default path. When \u003ccode\u003epdftotext\u003c/code\u003e yields no text\nlayer (scanned / export-locked / oversized), \u003cstrong\u003eGhostscript normalizes the PDF\nfirst\u003c/strong\u003e (\u003ccode\u003einternal/ocr.NormalizePDF\u003c/code\u003e, gs \u003ccode\u003epdfwrite\u003c/code\u003e → \u003ccode\u003evar/tmp\u003c/code\u003e, original\npreserved, artifact ≤ original): strips export-protection + shrinks so the\npdftotext fast path / tesseract fallback see a clean PDF. Clean PDFs skip gs\n(happy path stays fast). \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/6\" rel=\"nofollow\"\u003e#6\u003c/a\u003e,\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/102\" rel=\"nofollow\"\u003e#102\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eOQ3: \u003cstrong\u003ein\u003c/strong\u003e — duckdb-go (\u003ccode\u003epkg/duckdb\u003c/code\u003e, \u003ccode\u003ebin/jsonl/stats.go\u003c/code\u003e) for\nquantiles / JSONL count. Not a second graph. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/30\" rel=\"nofollow\"\u003e#30\u003c/a\u003e.\u003c/li\u003e\n\u003cli\u003eOQ4: YAML-first storage for leafs — deferred: JSON is ~10x faster to\nserialize and unambiguous; YAML only where humans edit files.\u003c/li\u003e\n\u003cli\u003eOQ5: \u003cstrong\u003ein\u003c/strong\u003e — fact \u003ccode\u003evalid_from\u003c/code\u003e / \u003ccode\u003evalid_to\u003c/code\u003e + \u003ccode\u003e--as-of\u003c/code\u003e / MCP \u003ccode\u003eas_of\u003c/code\u003e (D24).\nNot D16 \u003ccode\u003etemporal_freshness\u003c/code\u003e. \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/36\" rel=\"nofollow\"\u003e#36\u003c/a\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eMail pipeline (done)\u003c/h2\u003e\u003ca id=\"user-content-mail-pipeline-done\" class=\"anchor\" aria-label=\"Permalink: Mail pipeline (done)\" href=\"#mail-pipeline-done\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003ebin/mail/sync.go\u003c/code\u003e (Go, 8 workers) — paginated Gmail / OnlyOffice / M365\nGraph download. Gmail attachments key off \u003ccode\u003ebody.attachmentId\u003c/code\u003e, not MIME\n\u003ccode\u003epartId\u003c/code\u003e. M365 uses client-credentials + delta link (commit after success).\nM365 inline attachments are kept (#312): stored in \u003ccode\u003eattachments/\u003c/code\u003e under\n\u003ccode\u003einline-\u0026lt;hash8\u0026gt;-\u0026lt;name\u0026gt;\u003c/code\u003e, flagged \u003ccode\u003eisInline\u003c/code\u003e/\u003ccode\u003econtentId\u003c/code\u003e in \u003ccode\u003emessage.json\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebin/mail/import.go --from-raw\u003c/code\u003e — message.json → message.md; PDFs via\n\u003ccode\u003epdftotext -layout\u003c/code\u003e (~15ms); textless/scanned PDFs \u003ccode\u003epdftoppm\u003c/code\u003e + tesseract\n\u003ccode\u003eeng+deu\u003c/code\u003e. PDFs without a readable text layer are first normalized with\nGhostscript (\u003ccode\u003einternal/ocr.NormalizePDF\u003c/code\u003e, gs \u003ccode\u003epdfwrite\u003c/code\u003e; original preserved,\ngs artifact in \u003ccode\u003evar/tmp\u003c/code\u003e, removed after extraction). ICS sidecars\nLatin-1→UTF-8 normalized.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebin/brain/index.go --rebuild\u003c/code\u003e — fresh rebuild (repo corpus + mail) because ladybug\ncorrupts its WAL on bulk-insert into an already-indexed DB. Conversion and\nindexing stay separate for crash safety.\u003c/li\u003e\n\u003cli\u003eCompose \u003ccode\u003email-sync\u003c/code\u003e / \u003ccode\u003escripts/stack/start-mail-sync\u003c/code\u003e — ETL loop (default\n\u003ccode\u003eonlyoffice,gmail\u003c/code\u003e, 300s): download (existing mailsync binary) → bounded\nrunner pipeline import (#98) with per-run stats YAML; full rebuild only if\n\u003ccode\u003eMAIL_SYNC_INDEX=1\u003c/code\u003e. Bot digests (ai-bot) and case wrappers reuse sync/OAuth;\nthey do not replace the corpus path.\u003c/li\u003e\n\u003cli\u003eResult: 17,835 messages → 28,918 info leafs, FTS + HNSW healthy, searchable\nvia \u003ccode\u003ebin/brain/search.go\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eCI/CD pipeline (D15)\u003c/h2\u003e\u003ca id=\"user-content-cicd-pipeline-d15\" class=\"anchor\" aria-label=\"Permalink: CI/CD pipeline (D15)\" href=\"#cicd-pipeline-d15\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003e.github/workflows/ci.yml\u003c/code\u003e:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003ego vet + go test ./... (root module; packages without ladybug cgo)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ego test ./internal/brain/rank\u003c/code\u003e (cgo-free ranking + flag parser)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebin/facts/audit self\u003c/code\u003e (lexicon internal consistency; \u003ccode\u003ebin/facts/audit.go\u003c/code\u003e is the D14 wrapper)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebin/brain/eval.go\u003c/code\u003e via Zig (recall@5 ≥ 0.95) — the CI SoT.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebin/cgo/zig go build -tags system_ladybug\u003c/code\u003e (compile search with zig cc; fetches pinned zig+libs).\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp dir=\"auto\"\u003eFeedback loop: every commit → PR → CI → green/gate → merge. Same discipline as\n\u003ccode\u003edb/tech-poc\u003c/code\u003e: contract first where there is an OpenAPI/message shape.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eExecution order\u003c/h2\u003e\u003ca id=\"user-content-execution-order\" class=\"anchor\" aria-label=\"Permalink: Execution order\" href=\"#execution-order\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003escaffold repo (:done after this file + AGENTS.md + .gitignore + ci)\u003c/li\u003e\n\u003cli\u003egh repo create eSlider/2dph --private + initial commit + CI\u003c/li\u003e\n\u003cli\u003evendored skill integration (web-search, postgres, brain, diataxis-docs) — no remote links\u003c/li\u003e\n\u003cli\u003eschema + tools with TDD (kb + md + facts + brain)\u003c/li\u003e\n\u003cli\u003e~/.config/brain config\u003c/li\u003e\n\u003cli\u003ecorpus extraction (facts/info) — \u003cstrong\u003ein\u003c/strong\u003e: \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/18\" rel=\"nofollow\"\u003e#18\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003everify: web-search smoke, onlyoffice pg, md-db round-trip, eval, audit\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eGap to v1 (epic #16)\u003c/h2\u003e\u003ca id=\"user-content-gap-to-v1-epic-16\" class=\"anchor\" aria-label=\"Permalink: Gap to v1 (epic #16)\" href=\"#gap-to-v1-epic-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eRemaining: none for epic #16 (v1). Board:\n\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/16\" rel=\"nofollow\"\u003eepic #16\u003c/a\u003e,\nmilestone \u003ca href=\"https://git.produktor.io/eSlider/2dph/milestone/12\" rel=\"nofollow\"\u003ev1 detective brain\u003c/a\u003e.\nNarrative: \u003ca href=\"/eSlider/2dph/blob/main/docs/roadmap.md\"\u003edocs/roadmap.md\u003c/a\u003e.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eOrder\u003c/th\u003e\n\u003cth\u003eIssue\u003c/th\u003e\n\u003cth\u003eGap\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e1\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/14\" rel=\"nofollow\"\u003e#14\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003ein\u003c/strong\u003e — \u003ccode\u003ebin/brain/add.go\u003c/code\u003e / \u003ccode\u003ePOST /ingest\u003c/code\u003e write facts+info without deleting \u003ccode\u003ekb.lbug\u003c/code\u003e. Bulk corpus still \u003ccode\u003e--rebuild\u003c/code\u003e. Leftover Python (mail/facts) is not the living-graph blocker.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e2\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/17\" rel=\"nofollow\"\u003e#17\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003ein\u003c/strong\u003e — \u003ccode\u003e--hop N\u003c/code\u003e walks \u003ccode\u003eFROM_FILE\u003c/code\u003e → \u003ccode\u003eHAS_VERSION\u003c/code\u003e → \u003ccode\u003eAUTHORED\u003c/code\u003e (max 3).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e3\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/18\" rel=\"nofollow\"\u003e#18\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003ein\u003c/strong\u003e — \u003ccode\u003e--with-facts\u003c/code\u003e / \u003ccode\u003e--facts-json\u003c/code\u003e land \u003ccode\u003eroot=facts\u003c/code\u003e; \u003ccode\u003e--with-chats\u003c/code\u003e indexes \u003ccode\u003evar/corpus/chats/md\u003c/code\u003e. WhatsApp sync is in v1 via \u003ca href=\"https://git.produktor.io/eSlider/2dph/pulls/87\" rel=\"nofollow\"\u003e#87\u003c/a\u003e.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e4\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/15\" rel=\"nofollow\"\u003e#15\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003ein\u003c/strong\u003e — lever/loop documented (\u003ccode\u003esearch\u003c/code\u003e → \u003ccode\u003eget\u003c/code\u003e → \u003ccode\u003eaudit\u003c/code\u003e).\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e5\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/19\" rel=\"nofollow\"\u003e#19\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003ein\u003c/strong\u003e — CI recall SoT is \u003ccode\u003ebin/brain/eval.go\u003c/code\u003e via Zig.\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eDoes \u003cstrong\u003enot\u003c/strong\u003e block epic close: OQ4. OCR \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/6\" rel=\"nofollow\"\u003e#6\u003c/a\u003e, OQ1 \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/29\" rel=\"nofollow\"\u003e#29\u003c/a\u003e, OQ3 \u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/30\" rel=\"nofollow\"\u003e#30\u003c/a\u003e are \u003cstrong\u003ein\u003c/strong\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-03--l-93-формальные-проверки-url-identitycontradictionexcluded-middlesufficient-reason-gitea-232-epic-229\" class=\"anchor\" aria-label=\"Permalink: 2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)\" href=\"#2026-09-03--l-93-формальные-проверки-url-identitycontradictionexcluded-middlesufficient-reason-gitea-232-epic-229\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: перевести эмпирические URL-рецепты (#56) в машинные проверки законов\nVinogradov на уровне URL: identity (канонический URL — одно написание),\ncontradiction (¬(P∧¬P) на одном URL), excluded middle (нечёткие claim → не\nFACT, а open_questions), sufficient reason (verdict accept только на\nFACT-/OPEN- premises). Ядро — cgo-free \u003ccode\u003einternal/facts\u003c/code\u003e (по образцу\nAdjudicate/CheckFactRow), CLI — подкоманда \u003ccode\u003ebin/facts/audit.go formal\u003c/code\u003e\n(JSON facts/cards на stdin → JSON-флаги, exit 0/1).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003efacts.CanonicalURL\u003c/code\u003e — локальная канонизация URL по конвенции gator G-8.1 (lowercase scheme/host, default-port, utm_*/ref/gh_jid/token, сортировка query, без фрагмента/trailing slash); gator-модуль не импортируется\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/facts/formal.go\u003c/code\u003e: \u003ccode\u003eURLFact\u003c/code\u003e (id/url/claim/attr/neg/conf/from/to) + \u003ccode\u003eCheckURLIdentity\u003c/code\u003e (merge/flag weaken), \u003ccode\u003eCheckURLContradiction\u003c/code\u003e (reject; только confirmed, пересечение D24), \u003ccode\u003eCheckExcludedMiddle\u003c/code\u003e (weaken → open_questions), \u003ccode\u003eCheckSufficientReason\u003c/code\u003e (accept без FACT-/OPEN- premises → reject), \u003ccode\u003eCheckFormal\u003c/code\u003e (агрегат, детерминизм)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003ebin/facts/audit.go formal\u003c/code\u003e (shebang, hand-formatted): JSON {facts,cards} на stdin → {mode,ok,problems}; пример acceptance «вакансия активна» vs «вакансия closed» → contradiction verdict=reject\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003ecanonical_test.go\u003c/code\u003e (кросс-канал/utm/детерминизм/нормализация/ошибки) + \u003ccode\u003eformal_test.go\u003c/code\u003e (identity merge, contradiction acceptance + не-противоречия: same polarity/разный attr/weak/disjoint D24, excluded-middle fuzzy→OPEN, sufficient-reason accept premises)\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race ./internal/facts\u003c/code\u003e green\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eСовместимость с #56/#231: \u003ccode\u003ego test ./internal/facts\u003c/code\u003e + \u003ccode\u003ebin/facts/audit.go self\u003c/code\u003e зелёные (CI-гейт)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/facts/audit-recipes.md §Формальные проверки URL (L-9.3 #232) + PLAN.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test -race ./internal/facts/\u003c/code\u003e green, \u003ccode\u003ego vet ./internal/facts/\u003c/code\u003e clean,\n\u003ccode\u003e./bin/facts/audit.go self\u003c/code\u003e ok. Branch \u003ccode\u003efeat/facts-formal-checks#232\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-02--l-94-git-commits--commitpersonauthored-граф-gitea-233-epic-229\" class=\"anchor\" aria-label=\"Permalink: 2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)\" href=\"#2026-09-02--l-94-git-commits--commitpersonauthored-граф-gitea-233-epic-229\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: заполнить Commit/Person/AUTHORED (InitSchema write.go:130-133 уже создаёт\nтаблицы, писателя не было) из git-истории репо git.produktor.io — источник\nфактов «кто над чем работал, когда» для аудита L-9 и оси проекта сети #234.\nДизайн: \u003ca href=\"/eSlider/2dph/blob/main/docs/brain/graph-git-commits.md\"\u003edocs/brain/graph-git-commits.md\u003c/a\u003e.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eКанон git-коммита: \u003ccode\u003e(repo, sha)\u003c/code\u003e, \u003ccode\u003eCommit.id = \u0026lt;repo\u0026gt;:\u0026lt;sha\u0026gt;\u003c/code\u003e (repo = RepoName origin-remote); чтение — \u003ccode\u003egitlog.Log\u003c/code\u003e (go-git, тот же канон, что premises-корпус: merge пропущены, автор = Author, дата RFC3339)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/gitgraph\u003c/code\u003e (cgo-free): Resolve (root-скан/--repo), ReadAll, ToInputs (email lowercase — Person.id канон mail D-1 #257, dedup, сортировка по дате ASC), ComputeStats, WeakSubject\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/commitplan.go\u003c/code\u003e (cgo-free): CommitInput, MERGE-запросы commit/AUTHORED, commitAuthor (Person.id=email); \u003ccode\u003egitcommit.go\u003c/code\u003e (cgo): UpsertCommit/UpsertCommits (транзакция, идемпотентно)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003ebin/git/graph.go\u003c/code\u003e: dry-run/--commit, --repo/--root/--since/--limit, --skip-existing, live-holder guard, батчи; отчёт before→after Commit/Person/AUTHORED\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003ebin/git/facts.go\u003c/code\u003e (read-only): факт-карточки «кто работал над repo когда» + audit card (claim/premises/inference/gaps/verdict; слабый subject → weaken+OPEN)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: юнит cgo-free (commitplan + gitgraph: канон/маппинг/dedup/Resolve/ReadAll/WeakSubject/GroupFacts/BuildFacts) + cgo на живой Ladybug (temp DB): запись/перечитывание, повтор = 0 дублей, Person mail↔git сопряжение, сосуществование с Leaf/Message\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/brain/graph-git-commits.md + шаблон audit card в docs/facts/audit-recipes.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eLive-пилот: 2dph + gator dry-run → --commit в kb.lbug (бэкап, live-holder guard), idempotency-повтор, git-facts спот-чек\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test ./...\u003c/code\u003e + \u003ccode\u003ego vet ./...\u003c/code\u003e green (CI-режим);\n\u003ccode\u003ebin/cgo/zig go test -tags system_ladybug ./internal/brain/\u003c/code\u003e green (весь пакет);\n\u003ccode\u003ebin/cgo/zig go vet -tags system_ladybug ./internal/brain/\u003c/code\u003e green.\nBranch \u003ccode\u003efeat/git-commits#233\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-02--l-95-сеть-связей-кто-с-кем-и-через-кого-gitea-234-epic-229\" class=\"anchor\" aria-label=\"Permalink: 2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)\" href=\"#2026-09-02--l-95-сеть-связей-кто-с-кем-и-через-кого-gitea-234-epic-229\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: инструмент сети поверх готового графа (mail D-1 #257 + git L-9.4 #233):\nPerson↔Person по общим письмам/тредам/получателям (SENT/TO/CC/BCC/REPLY_TO) и\nобщим проектам (AUTHORED Commit.repo), с premises (Message.id/Commit.id) и\nverdict по audit-картам (accept/weaken); экспорт accept в CRM (ADR-0012 п.4).\nRead-only: ничего не пишет, схему не меняет. Дизайн:\n\u003ca href=\"/eSlider/2dph/blob/main/docs/brain/graph-network.md\"\u003edocs/brain/graph-network.md\u003c/a\u003e.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/network\u003c/code\u003e (cgo-free): BuildLinks — деривации mail (прямой контакт sender↔recipient, вес TO/CC/BCC, общий получатель, REPLY_TO-диалог, треды) + git (общий проект по AUTHORED Commit.repo), premises, период, verdict accept/weaken+OPEN, ранжирование\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRead-only запросы графа \u003ccode\u003einternal/network/query.go\u003c/code\u003e (cgo): LoadRows — Message/Person/рёбра SENT/TO/CC/BCC/REPLY_TO + Commit/AUTHORED → строки сети\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003ebin/network/network.go\u003c/code\u003e (read-only): \u003ccode\u003e--person\u003c/code\u003e (обязателен), \u003ccode\u003e--project\u003c/code\u003e, \u003ccode\u003e--since/--until\u003c/code\u003e, \u003ccode\u003e--limit\u003c/code\u003e, \u003ccode\u003e--json\u003c/code\u003e, \u003ccode\u003e--accept-only\u003c/code\u003e (CRM-экспорт YAML)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: юнит cgo-free (деривации/вес/вердикты/ранжирование/premises на synthetic Alice/Bob/Carol) + cgo на живой Ladybug (temp DB): LoadRows→BuildLinks end-to-end\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eLive-пилот: \u003ca href=\"mailto:eslider@gmail.com\"\u003eeslider@gmail.com\u003c/a\u003e / \u003ca href=\"mailto:andriy.oblivantsev@wheregroup.com\"\u003eandriy.oblivantsev@wheregroup.com\u003c/a\u003e на живой kb.lbug (read-only) — топ связей, premises, CRM-экспорт accept\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/brain/graph-network.md + PLAN.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test ./...\u003c/code\u003e + \u003ccode\u003ego vet ./...\u003c/code\u003e green (CI-режим);\n\u003ccode\u003ebin/cgo/zig go test -tags system_ladybug ./internal/network/ ./internal/brain/\u003c/code\u003e\ngreen (cgo). Branch \u003ccode\u003efeat/network#234\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-02--d-12-messageperson-граф-схема--write-путь-gitea-259-epic-257\" class=\"anchor\" aria-label=\"Permalink: 2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)\" href=\"#2026-09-02--d-12-messageperson-граф-схема--write-путь-gitea-259-epic-257\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: перенести conversation-канон (\u003ca href=\"https://git.produktor.io/eSlider/2dph/issues/99\" rel=\"nofollow\"\u003e#99\u003c/a\u003e,\n\u003ccode\u003einternal/canon\u003c/code\u003e) в рабочую Ladybug-схему kb.lbug: узел \u003cstrong\u003eMessage\u003c/strong\u003e + рёбра\n\u003cstrong\u003eSENT/TO/CC/BCC/REPLY_TO\u003c/strong\u003e рядом с существующими Leaf/Person; идемпотентная\nзапись (MERGE). Дизайн: \u003ca href=\"/eSlider/2dph/blob/main/docs/brain/graph-gator-mail.md\"\u003edocs/brain/graph-gator-mail.md\u003c/a\u003e\n(D-1.1 #258). По итогу почта в 2dph живёт как граф сущностей, а не Leaf-текст\nиз сырья.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eInitSchema\u003c/code\u003e аддитивно (\u003ccode\u003eIF NOT EXISTS\u003c/code\u003e): \u003ccode\u003eMessage\u003c/code\u003e node (id/thread_id/folder/subject/sent_at/gator_ref/body, PK id) + rel \u003ccode\u003eSENT (Person→Message)\u003c/code\u003e, \u003ccode\u003eTO/CC/BCC (Message→Person)\u003c/code\u003e, \u003ccode\u003eREPLY_TO (Message→Message)\u003c/code\u003e; Person/Leaf/Commit не тронуты\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/graphplan.go\u003c/code\u003e (cgo-free): \u003ccode\u003eMessageInput\u003c/code\u003e (canon.Message + folder/subject/gator_ref), \u003ccode\u003eplanGraph\u003c/code\u003e — уникальные Person (From→To→CC→BCC, name первого вхождения), рёбра = \u003ccode\u003ecanon.Edges()\u003c/code\u003e минус PART_OF/само-REPLY_TO; генерация MERGE-запросов (person/message/edge), \u003ccode\u003eedgeSchema\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/graph.go\u003c/code\u003e (cgo): \u003ccode\u003eUpsertMessage\u003c/code\u003e/\u003ccode\u003eUpsertMessages\u003c/code\u003e — пачка в одной транзакции, MERGE по message_id/email; Person name-мердж «последний sync побеждает» (SET в MERGE); gator_ref на Message\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: юнит cgo-free (план persons/edges, детерминизм, формы MERGE-запросов, sentAtText); cgo-тесты на живой Ladybug (temp DB): запись+перечитывание, повторный прогон = 0 дублей, name-мердж, сосуществование с Leaf + EnsureIndexes\u003c/td\u003e\n\u003ctd\u003edone — 8 юнит + 4 cgo зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: §8 «Реализация D-1.2» в дизайн-доке (схема + write-путь)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eИмпорт gator kind=mail → граф (wheregroup 3984)\u003c/td\u003e\n\u003ctd\u003e#260 (следующий)\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ebin/cgo/zig go test -race -tags system_ladybug ./internal/brain/\u003c/code\u003e green\n(весь пакет, ~6 мин); \u003ccode\u003ego vet ./...\u003c/code\u003e + \u003ccode\u003ego test ./...\u003c/code\u003e green (CI-режим);\n\u003ccode\u003ego test ./internal/canon/\u003c/code\u003e — 14 тестов green (canon #99 не изменён).\nBranch \u003ccode\u003efeat/graph-schema#259\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e (cc40235).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-18 — Go-only write path sync (gitea #41)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-18--go-only-write-path-sync-gitea-41\" class=\"anchor\" aria-label=\"Permalink: 2026-08-18 — Go-only write path sync (gitea #41)\" href=\"#2026-08-18--go-only-write-path-sync-gitea-41\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: consolidate the most advanced version here and in sync with GitHub main.\nSources: GitHub main \u003ccode\u003eb1e6953\u003c/code\u003e + arc-1 \u003ccode\u003e/mnt/8TB/projects/ai/2dph\u003c/code\u003e Go-write WIP +\nedelweiss \u003ccode\u003e/home/devops/projects/2dph\u003c/code\u003e M365 WIP. Prefer Go; Python only for A/B.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003esync issues via tea (gitea #41 assigned)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePhase 1: branch \u003ccode\u003efeat/brain-go-writepath\u003c/code\u003e + arc-1/edelweiss WIP\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePhase 2: finish Go write path (write.go/corpus.go/index/add/mailconv) TDD\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eyaml-seed secret/noise filter; compose/CI → Go (no Python INSTALL)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePhase 3: PR → CI green → merge GitHub main; docs (D6 reversal)\u003c/td\u003e\n\u003ctd\u003edone — PR #42, merged \u003ccode\u003e2cea0e7\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePhase 4: go-ollama + go-xls PR/merge; verify go-env/go-hocr/go-onlyoffice\u003c/td\u003e\n\u003ctd\u003edone — PR #4 (\u003ccode\u003e325c4e2\u003c/code\u003e), PR #1 (\u003ccode\u003e165d25d\u003c/code\u003e)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePhase 5: deploy brain :8630 + wire MCP into opencode.json\u003c/td\u003e\n\u003ctd\u003edone — brain live (7754 leafs), MCP wired; PR #43 merged \u003ccode\u003ea1880de\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNote: CI found + fixed 3 real bugs in the port (modeldir HF-cache default +\nsnapshot weight selection, secret/noise filter false positives).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eDeploy findings (PR #43): fts-extension install needs writable+execable HOME\n(\u003ccode\u003e/data\u003c/code\u003e, not noexec \u003ccode\u003e/tmp\u003c/code\u003e); serve must bind \u003ccode\u003eKB_HOST=0.0.0.0\u003c/code\u003e for compose\npublishing; corpus walk must skip permission-denied entries; leaf text needs\nUTF-8 sanitization before FTS.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eDecisions (2026-08-18): exclude arc-1 junk (external/, docs/chats/, bin/chats.bin,\nqa/load_test_*.py); Python fully removed (2026-08-19); M365 WIP same PR;\ngo-ollama+go-xls now, rest verify.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — Typed config (gitea #90, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--typed-config-gitea-90-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — Typed config (gitea #90, epic #88)\" href=\"#2026-08-22--typed-config-gitea-90-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: replace ad-hoc \u003ccode\u003eos.Getenv\u003c/code\u003e in the service layer with one typed config\nloaded by a stack, keeping legacy env names working. Dep\n\u003ccode\u003egithub.com/eslider/go-config@v0.4.0\u003c/code\u003e; new \u003ccode\u003einternal/config\u003c/code\u003e (\u003ccode\u003econfig.go\u003c/code\u003e,\n\u003ccode\u003eload.go\u003c/code\u003e, \u003ccode\u003eyaml.go\u003c/code\u003e, \u003ccode\u003eenv.go\u003c/code\u003e) — TDD offline fixtures, no network/db.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003einternal/config load stack + merge + lower+alnum + legacy mapping (TDD)\u003c/td\u003e\n\u003ctd\u003edone — tests green\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ewire pkg/httpapi, internal/brain, bin/brain/{serve,index,watch}, internal/mailsync, internal/websearch\u003c/td\u003e\n\u003ctd\u003edone — no ad-hoc env reads in those packages\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ecommitted \u003ccode\u003eetc/brain/config.yml\u003c/code\u003e template + gitignore \u003ccode\u003econfig.local.yml\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003elegacy env map\u003c/td\u003e\n\u003ctd\u003eKB_ROOT→Root, KB_PORT→Port, KB_HOST→Host, KB_WORKERS→Workers, KB_PPROF→Pprof, KB_SEARCH_CMD→SearchCmd, KBSEARCH_PORT→SearchDaemonPort, KBSEARCH_NO_DAEMON→SearchNoDaemon, KBSEARCH_MODEL→Model, HF_HOME→HFHome, KB_BUFFER_POOL→BufferPool, KBTEST_EPS→Eps, KB_INDEX_ALLOW_LIVE→IndexAllowLive, KB_WATCH_INTERVAL→WatchInterval, KB_WATCH_DIRS→WatchDirs, OO_CLI→OOCLI, BRAIN_SEARCH_{URL,USER,PASS,CACHE,ENV}→Search.*\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNon-goals kept: graph/search semantics unchanged. Pending: OnlyOffice CLI\nconfig wiring from \u003ccode\u003ebin/mail/*\u003c/code\u003e (out of #90 scope) — \u003ccode\u003einternal/mailsync.SetOOCLI\u003c/code\u003e\nis ready for it.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--mime-parser-swap-in-mailconv-gitea-95-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)\" href=\"#2026-08-22--mime-parser-swap-in-mailconv-gitea-95-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: replace \u003ccode\u003ejhillyerd/enmime/v2\u003c/code\u003e with \u003ccode\u003eemersion/go-message\u003c/code\u003e in\n\u003ccode\u003einternal/mailconv\u003c/code\u003e (parser-equivalent swap, no behaviour change to graph/search).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003efixtures: plain / alternative / mixed / nested(alternative+mixed) / charset(iso-8859-1 QP) \u003ccode\u003e.eml\u003c/code\u003e in \u003ccode\u003einternal/mailconv/testdata/\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eparseEML\u003c/code\u003e via \u003ccode\u003emessage.Read\u003c/code\u003e + \u003ccode\u003eentity.Walk()\u003c/code\u003e recursion; text/plain→TextBody, text/html→HTMLBody, attachment leaves by filename/disposition\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003echarset via \u003ccode\u003ego-message/charset\u003c/code\u003e (x/text ianaindex/htmlindex) — blank import\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003epublic API stable: \u003ccode\u003eFromEML\u003c/code\u003e/\u003ccode\u003eFromRaw\u003c/code\u003e/\u003ccode\u003eConvertAttachment\u003c/code\u003e/\u003ccode\u003eMessage\u003c/code\u003e/\u003ccode\u003eAttachment\u003c/code\u003e unchanged; \u003ccode\u003ewriteMessageJSON\u003c/code\u003e/\u003ccode\u003ewriteEMLAttachments\u003c/code\u003e internal\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ego mod tidy\u003c/code\u003e → enmime absent from go.mod\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ego build ./internal/... ./pkg/...\u003c/code\u003e + \u003ccode\u003ego test -race ./internal/mailconv/... ./pkg/...\u003c/code\u003e green\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e-benchmem before(env v2.4.1)/after(emersion v0.18.2): ~6–9× faster ns/op, B/op ≈ half, allocs ≈ 40–55% lower\u003c/td\u003e\n\u003ctd\u003edone — bench in \u003ccode\u003eeml_test.go\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eBranch \u003ccode\u003efeat/mime-emersion#95\u003c/code\u003e: c8f7120 (test) → 7015ffc (feat) → 9b4425d (chore/deps).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--mailsync-v1-мелкие-фиксы-gitea-111-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)\" href=\"#2026-08-22--mailsync-v1-мелкие-фиксы-gitea-111-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: small defects found porting mailsync v1 — \u003ccode\u003emailconv.FromEML\u003c/code\u003e folder\nextraction and the Dockerfile \u003ccode\u003epkg/\u003c/code\u003e copy.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003etest (red): \u003ccode\u003eTestFromEMLFolderNestedLayout\u003c/code\u003e — \u003ccode\u003e\u0026lt;root\u0026gt;/\u0026lt;folder\u0026gt;/\u0026lt;id\u0026gt;/\u0026lt;id\u0026gt;.eml\u003c/code\u003e asserts folder=\u003ccode\u003e\u0026lt;folder\u0026gt;\u003c/code\u003e via \u003ccode\u003emessage.json\u003c/code\u003e + \u003ccode\u003emessage.md\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eemlFolder(root,p)\u003c/code\u003e: parent of the \u003ccode\u003e\u0026lt;id\u0026gt;\u003c/code\u003e dir (grandparent of \u003ccode\u003e.eml\u003c/code\u003e) for the nested layout; flat \u003ccode\u003e\u0026lt;root\u0026gt;/*.eml\u003c/code\u003e keeps immediate dir; applied in \u003ccode\u003eFromEML\u003c/code\u003e + \u003ccode\u003ewriteMessageJSON\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eDockerfile \u003ccode\u003epkg/\u003c/code\u003e copy: release/v1 root \u003ccode\u003eDockerfile\u003c/code\u003e already \u003ccode\u003eCOPY . .\u003c/code\u003e (covers \u003ccode\u003epkg/cli\u003c/code\u003e for \u003ccode\u003einternal/ocr\u003c/code\u003e); no \u003ccode\u003edeploy/mail-watcher.Dockerfile\u003c/code\u003e in release/v1 (that file lives on main via #107, not an ancestor) — no change needed\u003c/td\u003e\n\u003ctd\u003ealready-fixed (verify only)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ego test ./internal/mailconv/... ./internal/mailsync/... ./internal/ocr/...\u003c/code\u003e + \u003ccode\u003e./internal/...\u003c/code\u003e green; \u003ccode\u003egofmt -l\u003c/code\u003e clean; \u003ccode\u003ego vet\u003c/code\u003e clean\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eBranch \u003ccode\u003efix/mailsync-v1#111\u003c/code\u003e → PR into \u003ccode\u003erelease/v1\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--pr--merge-featimap-mailsync-v1--releasev1-gitea-112-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)\" href=\"#2026-08-23--pr--merge-featimap-mailsync-v1--releasev1-gitea-112-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: land the v1 generic IMAP source (from #106) that was never merged:\n\u003ccode\u003einternal/mailsync/{imap,watch}.go\u003c/code\u003e + offline tests + CLI/flags wiring +\n\u003ccode\u003ebin/mail/watch\u003c/code\u003e entrypoint + \u003ccode\u003edeploy/mail-watcher.Dockerfile\u003c/code\u003e.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003edelta audit vs release/v1 (756f514): missing = imap source files, watch loop, CLI flags (\u003ccode\u003e--once/--brain/--ocr/--local\u003c/code\u003e), \u003ccode\u003eIMAP_*\u003c/code\u003e env parsing, \u003ccode\u003eSyncConfig.IMAP\u003c/code\u003e, \u003ccode\u003ebin/mail/watch\u003c/code\u003e, \u003ccode\u003edeploy/mail-watcher.Dockerfile\u003c/code\u003e, \u003ccode\u003ego-imap/v2\u003c/code\u003e+\u003ccode\u003ego-sasl\u003c/code\u003e deps\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eoverlap check: brain write-path fixes (\u003ccode\u003einternal/brain/*\u003c/code\u003e, \u003ccode\u003emailconv.FromEML\u003c/code\u003e) already on release/v1 via #109/#110/#111 — NOT re-ported; old \u003ccode\u003egmail.go\u003c/code\u003e/\u003ccode\u003em365.go\u003c/code\u003e/\u003ccode\u003eonlyoffice.go\u003c/code\u003e untouched and compiling\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eport: \u003ccode\u003einternal/mailsync/imap.go\u003c/code\u003e + \u003ccode\u003eimap_test.go\u003c/code\u003e + \u003ccode\u003ewatch.go\u003c/code\u003e, cli.go/sync.go wiring, \u003ccode\u003ebin/mail/watch/main.go\u003c/code\u003e, Dockerfile, \u003ccode\u003ego.mod\u003c/code\u003e/\u003ccode\u003ego.sum\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003esource selection: \u003ccode\u003e--source imap\u003c/code\u003e → \u003ccode\u003eIMAPEnv(envVars)\u003c/code\u003e → \u003ccode\u003eSyncConfig.IMAP\u003c/code\u003e → \u003ccode\u003enewIMAPSources\u003c/code\u003e; \u003ccode\u003ecfg.Raw=true\u003c/code\u003e (always \u003ccode\u003e.eml\u003c/code\u003e); readEnv honours \u003ccode\u003eIMAP_*\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003etests (offline, synthetic Alice/Bob): \u003ccode\u003eSanitizeFolder\u003c/code\u003e, \u003ccode\u003eIMAPEnv\u003c/code\u003e, \u003ccode\u003eparseIMAPMessage\u003c/code\u003e (emersion parser), \u003ccode\u003ehasNoSelect\u003c/code\u003e; no network\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ego test ./internal/...\u003c/code\u003e + \u003ccode\u003e./internal/mailsync/...\u003c/code\u003e green; \u003ccode\u003ego vet\u003c/code\u003e clean; \u003ccode\u003egofmt -l\u003c/code\u003e clean (shebang-protected files excluded)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ebranch \u003ccode\u003emerge/imap-mailsync-v1#112\u003c/code\u003e off \u003ccode\u003eorigin/release/v1\u003c/code\u003e → PR into \u003ccode\u003erelease/v1\u003c/code\u003e, CI green\u003c/td\u003e\n\u003ctd\u003edone (open, not merged)\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eBranch \u003ccode\u003emerge/imap-mailsync-v1#112\u003c/code\u003e → PR into \u003ccode\u003erelease/v1\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--reasoner-client--api-client-canon-gitea-93-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)\" href=\"#2026-08-23--reasoner-client--api-client-canon-gitea-93-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: apply \u003ccode\u003eskills/api-client/SKILL.md\u003c/code\u003e (go-ollama canon) to\n\u003ccode\u003einternal/reasoner/client.go\u003c/code\u003e, first candidate. Removes the 16× \u003ccode\u003emap[string]any\u003c/code\u003e\n(typed \u003ccode\u003eTool\u003c/code\u003e/\u003ccode\u003eToolSchema\u003c/code\u003e).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003etest: offline httptest typed client — chat round-trip, tool-call, error/status, ctx cancel, NDJSON doStream\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003e-race\u003c/code\u003e green\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003efeat: \u003ccode\u003eClient{hc,cfg}\u003c/code\u003e + \u003ccode\u003eNew(cfg)\u003c/code\u003e, \u003ccode\u003edoJSON\u003c/code\u003e/\u003ccode\u003edoStream\u003c/code\u003e core, typed \u003ccode\u003eChatRequest\u003c/code\u003e/\u003ccode\u003eChatResponse\u003c/code\u003e, ctx-first\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003econfig: \u003ccode\u003ereasoner.Config\u003c/code\u003e + \u003ccode\u003eLoadEnv()\u003c/code\u003e accessor; bench.go reads \u003ccode\u003eREASONER_BASE_URL\u003c/code\u003e from it\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ebench.go: typed client, \u003ccode\u003ectx\u003c/code\u003e; bake-off behaviour preserved\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003emap[string]any\u003c/code\u003e in \u003ccode\u003einternal/reasoner/client.go\u003c/code\u003e → 0\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePLAN.md #93 status\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNotes: rebased onto \u003ccode\u003erelease/v1\u003c/code\u003e \u003ccode\u003e36f5ec4\u003c/code\u003e (after #90 landed). \u003ccode\u003einternal/config\u003c/code\u003e\ncarries no reasoner fields, so a minimal \u003ccode\u003ereasoner.LoadEnv()\u003c/code\u003e accessor (env →\n\u003ccode\u003eConfig\u003c/code\u003e) is kept; wiring \u003ccode\u003eREASONER_BASE_URL\u003c/code\u003e into \u003ccode\u003einternal/config.Config\u003c/code\u003e is\ndeferred. Bench shebang is \u003ccode\u003egofmt\u003c/code\u003e-protected (not reformatted).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--go-config-wave-2-tools--tests-gitea-91-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)\" href=\"#2026-08-22--go-config-wave-2-tools--tests-gitea-91-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: extend \u003ccode\u003einternal/config\u003c/code\u003e to the remaining tools/packages, add a \u003ccode\u003ereasoner\u003c/code\u003e\nsection and retire the ad-hoc \u003ccode\u003eos.Getenv\u003c/code\u003e config reads (KB_\u003cem\u003e, BRAIN_SEARCH_\u003c/em\u003e,\nREASONER_*, HF_HOME, model, port/workers…).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003econfig.ReasonerConfig{BaseURL,Model,Device}\u003c/code\u003e + \u003ccode\u003eConfig.Reasoner\u003c/code\u003e section + defaults\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003elegacy map: REASONER_BASE_URL→Reasoner.BaseURL, REASONER_MODEL→Reasoner.Model, REASONER_DEVICE→Reasoner.Device\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ereasoner client config from typed stack: \u003ccode\u003ereasoner.Configure(cfg)\u003c/code\u003e + \u003ccode\u003eFromTyped\u003c/code\u003e; \u003ccode\u003eLoadEnv()\u003c/code\u003e reads \u003ccode\u003eactive\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ebin/reasoner/bench.go\u003c/code\u003e loads \u003ccode\u003econfig.Load\u003c/code\u003e + \u003ccode\u003eConfigure\u003c/code\u003e; client tests green\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/web/search.go\u003c/code\u003e: BRAIN_SEARCH_CACHE/ENV → \u003ccode\u003ecfg.Search.Cache/Env\u003c/code\u003e (HOME fallback kept)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/brain/model.go\u003c/code\u003e: HF_HOME/KB_ROOT → \u003ccode\u003ecfg.HFHome/cfg.Root\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eetc/brain/config.yml\u003c/code\u003e template gains \u003ccode\u003ereasoner:\u003c/code\u003e section\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD fixtures: \u003ccode\u003ereasoner\u003c/code\u003e deep-merge in \u003ccode\u003etestdata/basic\u003c/code\u003e, legacy + defaults tests, \u003ccode\u003eisolateEnv\u003c/code\u003e covers REASONER_*\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race ./internal/... ./pkg/...\u003c/code\u003e green\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNotes: \u003ccode\u003einternal/chat/paths.go\u003c/code\u003e KB_ROOT + \u003ccode\u003epkg/repo/exec.go\u003c/code\u003e KB_ROOT left as\nad-hoc env reads — chat sources are secret-heavy (no config accessor yet) and\n\u003ccode\u003epkg/\u003c/code\u003e must not import \u003ccode\u003einternal\u003c/code\u003e (overlaps #92 \u003ccode\u003epkg/utils\u003c/code\u003e). Bench/web shebangs\nare \u003ccode\u003egofmt\u003c/code\u003e-protected (not reformatted).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--pkgutils--static-typing-hotspots-gitea-92-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)\" href=\"#2026-08-22--pkgutils--static-typing-hotspots-gitea-92-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: promote duplicated helpers into public \u003ccode\u003epkg/utils\u003c/code\u003e with static typing and\nkill the \u003ccode\u003emap[string]any\u003c/code\u003e / \u003ccode\u003einterface{}\u003c/code\u003e HTTP and root-resolution hotspots.\n\u003ccode\u003epkg/utils\u003c/code\u003e stays internal-free (D29, primitives only).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epkg/utils/root.go\u003c/code\u003e \u003ccode\u003eRoot()\u003c/code\u003e — KB_ROOT or walk-up \u003ccode\u003e.git\u003c/code\u003e/\u003ccode\u003evar\u003c/code\u003e, single source of truth; \u003ccode\u003epkg/repo.Root()\u003c/code\u003e and \u003ccode\u003einternal/chat.Root()\u003c/code\u003e delegate\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epkg/utils/json.go\u003c/code\u003e \u003ccode\u003eDoJSON\u003c/code\u003e/\u003ccode\u003eGetJSON\u003c/code\u003e — typed request + 2xx check + decode, \u003ccode\u003eopts\u003c/code\u003e for auth; wired into \u003ccode\u003einternal/reasoner.Client.doJSON\u003c/code\u003e, \u003ccode\u003einternal/websearch.Fetch\u003c/code\u003e, \u003ccode\u003einternal/chat.cdpTabs\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epkg/utils/str.go\u003c/code\u003e \u003ccode\u003eSnippet\u003c/code\u003e/\u003ccode\u003eOr\u003c/code\u003e — \u003ccode\u003ereasoner.snippet\u003c/code\u003e, \u003ccode\u003echat.orDefault\u003c/code\u003e removed\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD fixtures: \u003ccode\u003epkg/utils/{root,json,str}_test.go\u003c/code\u003e offline (httptest, t.TempDir)\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race ./pkg/... ./internal/...\u003c/code\u003e green, \u003ccode\u003ego vet\u003c/code\u003e clean, CGO ladybug build green\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNotes: \u003ccode\u003epkg/httpapi\u003c/code\u003e write-side (\u003ccode\u003ewriteJSON\u003c/code\u003e/\u003ccode\u003ewriteRaw\u003c/code\u003e) is a different direction\n(server response, not client decode) and was left as-is. Remaining\n\u003ccode\u003emap[string]any\u003c/code\u003e/\u003ccode\u003einterface{}\u003c/code\u003e decode sites in \u003ccode\u003einternal/brain\u003c/code\u003e, \u003ccode\u003einternal/chat\u003c/code\u003e\n(MCP/JSONL) and \u003ccode\u003einternal/config\u003c/code\u003e are domain-shaped and out of scope for this\npass.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--ladybug-write-path-hardening--soak-gitea-109-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)\" href=\"#2026-08-22--ladybug-write-path-hardening--soak-gitea-109-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: kill the silent in-process Ladybug death on \u003ccode\u003ePOST /ingest\u003c/code\u003e (~10% of\nwrites, ExitCode=0, no Go panic) and prove it with a soak test.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eRoot cause (release/v1): \u003ccode\u003eHTTP.Ingest\u003c/code\u003e opened a \u003cstrong\u003esecond\u003c/strong\u003e handle on the live\n\u003ccode\u003ekb.lbug\u003c/code\u003e (\u003ccode\u003eOpenWritable\u003c/code\u003e) while the serve read handle stayed open — liblbug\ndies silently when the same file is double-opened inside one process. The\nexisting close→reopen swap (\u003ccode\u003erefreshBrain\u003c/code\u003e) also ran unsynchronized against\nconcurrent handlers: any reader mid-query could execute a statement against a\njust-closed \u003ccode\u003e*lbug.Connection\u003c/code\u003e (C use-after-close; frame\n\u003ccode\u003eLocalNodeTable::isVisible ← HashIndex::lookupInPersistentIndex\u003c/code\u003e). Error paths\nleft the serve handle stale for the process lifetime.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebrainMu sync.RWMutex\u003c/code\u003e guards global db/conn; readers (Get/Stats/Audit, queryFTS/queryVector/attachHops, lookupLeaf/leafStats) hold RLock\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eIngest serialized (\u003ccode\u003eingestMu\u003c/code\u003e), holds write lock over close→write→reopen; \u003ccode\u003ecloseBrainLocked()\u003c/code\u003e runs BEFORE \u003ccode\u003eOpenWritable()\u003c/code\u003e (no same-file double-open)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRestore on every exit path (\u003ccode\u003eingestWriteLocked\u003c/code\u003e + deferred restore): write-stage errors no longer leave a stale/nil serve handle\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eembedIngestLeafs\u003c/code\u003e seam — embed step extracted as package var, tests run full Ingest offline (no HF model/daemon); singleton-cache (#110) replaces only its body\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eSoak: \u003ccode\u003eTestIngestSoakWritePath\u003c/code\u003e 200 × real \u003ccode\u003e/ingest\u003c/code\u003e + 8 concurrent readers (Stats/Audit/Get/FTS/vector), temp KB_ROOT, offline\u003c/td\u003e\n\u003ctd\u003ered before fix (native segfault), green after\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRace proofs: \u003ccode\u003eTestIngestSwapRaceSafe\u003c/code\u003e, \u003ccode\u003eTestIngestWriteRestoresServeConnOnError\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003egreen under \u003ccode\u003e-race\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerify: \u003ccode\u003ebin/cgo/zig go test -race -tags system_ladybug ./internal/brain/\u003c/code\u003e\ngreen; \u003ccode\u003ego vet\u003c/code\u003e clean; zig build of \u003ccode\u003ebin/brain/search.go\u003c/code\u003e OK. Same-file\ndouble-open stays forbidden inside one process; bulk rebuild keeps its own\nprocess (\u003ccode\u003ebin/brain/index.go\u003c/code\u003e). Out of scope: corpus CLI path (single-goroutine\nindex process), model singleton cache (#110).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--etl-реестр-обработчиков--безопасный-walker-gitea-96-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)\" href=\"#2026-08-23--etl-реестр-обработчиков--безопасный-walker-gitea-96-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: base layer for sync-ETL. A concurrency-safe registry of per-format ETL\nhandlers plus a hardened file walker (path traversal / symlink-escape /\ndepth-limit / binary-huge-file skip). Runner (#98) and source-adapters (#97)\nbuild on these.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/etl/registry.go\u003c/code\u003e: \u003ccode\u003eRegistry\u003c/code\u003e (RWMutex) mapping handler key → \u003ccode\u003eHandler{Name() string; Handle(ctx, path) error}\u003c/code\u003e; \u003ccode\u003eRegister\u003c/code\u003e rejects duplicate/empty keys, \u003ccode\u003eLookup\u003c/code\u003e/\u003ccode\u003eNames\u003c/code\u003e(sorted)/\u003ccode\u003eLen\u003c/code\u003e — concurrent-safe\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/etl/walker.go\u003c/code\u003e: \u003ccode\u003eWalkFiles(root, WalkOptions{MaxDepth, MaxBytes, SkipBinary, Exts})\u003c/code\u003e → deterministic sorted \u003ccode\u003e[]File{Path,Rel,Size}\u003c/code\u003e; rejects \u003ccode\u003e..\u003c/code\u003e traversal, never follows symlinked dirs, rejects symlink escapes outside root, honors depth limit, skips huge/binary files\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/markdown.WalkMarkdown\u003c/code\u003e refactored to the hardened walker (Exts filter)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/etl/etl_test.go\u003c/code\u003e — register/lookup/dup/sorted/concurrent(-race), deterministic order, traversal rejected, symlink-to-outside rejected, depth limit, large-file skip, binary skip, ext filter, missing-root error\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race ./internal/...\u003c/code\u003e green, \u003ccode\u003ego vet\u003c/code\u003e clean, gofmt clean\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNotes: scope kept to registry + walker only; pipeline rewiring is #97/#98.\n\u003ccode\u003einternal/mailconv\u003c/code\u003e and \u003ccode\u003ebin/*\u003c/code\u003e walkers stay legacy until a source adapter owns\nthem (single-implementation rule D33) — cutover tracked in #98.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--source-adapters-gitea-97--bounded-etl-runner-gitea-98-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)\" href=\"#2026-08-25--source-adapters-gitea-97--bounded-etl-runner-gitea-98-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: unified \u003ccode\u003eSource\u003c/code\u003e adapter layer + the bounded pipeline runner that wires\n\u003ccode\u003eSource → Decode(Registry) → Transform → Load\u003c/code\u003e with backpressure, per-run stats\nYAML and graceful shutdown.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/source\u003c/code\u003e: \u003ccode\u003eSource{Name(); Fetch(ctx,cursor)→[]Blob,Cursor,error}\u003c/code\u003e, \u003ccode\u003eBlob{ID,Kind,Path}\u003c/code\u003e, \u003ccode\u003eCursor\u003c/code\u003e; \u003ccode\u003eSync(ctx,src,handle,Options{StatePath})→Stats{Fetched,New,Skipped}\u003c/code\u003e — sha256 seen-set dedup + atomic checkpoints, mid-batch resume\u003c/td\u003e\n\u003ctd\u003edone (#97)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003esource.Disk\u003c/code\u003e: re-lists a corpus dir of \u003ccode\u003e.eml\u003c/code\u003e, content-hash identity, cursor never advances (driver dedups)\u003c/td\u003e\n\u003ctd\u003edone (#97)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/runner\u003c/code\u003e: bounded channels between stages (backpressure), \u003ccode\u003eerrgroup.WithContext\u003c/code\u003e first-error cancel, wg-accounted goroutines, graceful shutdown; reuses \u003ccode\u003esource.Sync\u003c/code\u003e + \u003ccode\u003eetl.Registry\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRunner \u003ccode\u003eReport\u003c/code\u003e: source stats + per-handler ok/failed counts, serialized as stats YAML (\u003ccode\u003estartedAt/duration/source/handlers\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/runner/run.go\u003c/code\u003e: CLI daemon over the disk corpus; \u003ccode\u003email\u003c/code\u003e handler → \u003ccode\u003emailconv.FromEMLFile\u003c/code\u003e (per-blob .eml→md); \u003ccode\u003e--interval\u003c/code\u003e loop + SIGINT/SIGTERM graceful shutdown\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCompose \u003ccode\u003email-sync\u003c/code\u003e migrates to the runner (\u003ccode\u003ecommand: [\"runner\",\"300\"]\u003c/code\u003e); \u003ccode\u003eMAIL_SYNC_SRC/ENV/INDEX\u003c/code\u003e semantics preserved (\u003ccode\u003eINDEX=1\u003c/code\u003e = full brain rebuild); download stays on the existing mailsync binary (source-adapter mapping for onlyoffice/gmail = follow-up)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/runner/runner_test.go\u003c/code\u003e — e2e disk source smoke (stats YAML round-trip), bounded in-flight backpressure, graceful shutdown on cancel (-race), unknown-kind error; \u003ccode\u003einternal/mailconv\u003c/code\u003e \u003ccode\u003eFromEMLFile\u003c/code\u003e idempotence\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race\u003c/code\u003e green, \u003ccode\u003ego vet\u003c/code\u003e clean, \u003ccode\u003edocker compose config\u003c/code\u003e parses\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-22 — singleton-cache ingest model (gitea #110)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-22--singleton-cache-ingest-model-gitea-110\" class=\"anchor\" aria-label=\"Permalink: 2026-08-22 — singleton-cache ingest model (gitea #110)\" href=\"#2026-08-22--singleton-cache-ingest-model-gitea-110\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: kill the ~0.9 GiB/\u003ccode\u003e/ingest\u003c/code\u003e leak. \u003ccode\u003eHTTP.Ingest\u003c/code\u003e called \u003ccode\u003eLoadModel()\u003c/code\u003e per\nrequest and \u003ccode\u003edefer model.Close()\u003c/code\u003e; \u003ccode\u003eStaticModel.Close()\u003c/code\u003e frees only the\ntokenizer, not the safetensors matrix (measured 3 ingests: RSS 3.5→6.1 GiB;\n~900-email backfill = OOM).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/ingestmodel.go\u003c/code\u003e: \u003ccode\u003eembedder\u003c/code\u003e interface (no Close — structural guarantee), \u003ccode\u003eingestModelCache\u003c/code\u003e (mutex, failed loads not cached → retry next request), \u003ccode\u003egetIngestModel()\u003c/code\u003e process-wide singleton, \u003ccode\u003eembedLeafs()\u003c/code\u003e extracted from Ingest\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/http.go\u003c/code\u003e Ingest: \u003ccode\u003eLoadModel()+defer Close()\u003c/code\u003e → \u003ccode\u003egetIngestModel()\u003c/code\u003e + \u003ccode\u003eembedLeafs\u003c/code\u003e; shared model never closed per request\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/brain/ingestmodel_test.go\u003c/code\u003e — single load across consecutive requests, 16-goroutine concurrent load-once, zero Close of shared model, retry-after-loader-error, pre-embedded skip\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ezig go test -race -tags system_ladybug ./internal/brain/\u003c/code\u003e green, \u003ccode\u003ego vet\u003c/code\u003e clean both tag modes, CGO search build green\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eNotes: other \u003ccode\u003eLoadModel()\u003c/code\u003e callers (\u003ccode\u003ebin/brain/add.go\u003c/code\u003e, \u003ccode\u003eindex.go\u003c/code\u003e,\n\u003ccode\u003ebin/facts/*\u003c/code\u003e) are one-shot CLI processes — unchanged. Diff kept strictly to\nmodel caching; connection lifecycle in the same function is #109 territory.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-23 — shared embedding model on the search path (gitea #335)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-23--shared-embedding-model-on-the-search-path-gitea-335\" class=\"anchor\" aria-label=\"Permalink: 2026-09-23 — shared embedding model on the search path (gitea #335)\" href=\"#2026-09-23--shared-embedding-model-on-the-search-path-gitea-335\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: same fix as #110, extended to \u003ccode\u003e/search\u003c/code\u003e. \u003ccode\u003einternal/brain/search.go\u003c/code\u003e\n\u003ccode\u003eembedQuery\u003c/code\u003e still called \u003ccode\u003eloadModel()\u003c/code\u003e + \u003ccode\u003edefer model.Close()\u003c/code\u003e on every\nrequest, and \u003ccode\u003eStaticModel.Close()\u003c/code\u003e freed only the tokenizer — so each search\nretained ~490 MiB inuse (~980 MiB allocated) until GC, and a live \u003ccode\u003ebrain-serve\u003c/code\u003e\ngrew to 13 GiB (RSS 1.8 + swap 11.2) over the DB's 11 MB.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/embedmodel.go\u003c/code\u003e (renamed from \u003ccode\u003eingestmodel.go\u003c/code\u003e): \u003ccode\u003eembedder\u003c/code\u003e (no Close), \u003ccode\u003eembedModelCache\u003c/code\u003e, \u003ccode\u003egetEmbedModel()\u003c/code\u003e process-wide singleton; now backs /ingest, the /search in-process fallback AND ANN extract\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003esearch.go\u003c/code\u003e \u003ccode\u003eembedQuery\u003c/code\u003e → new \u003ccode\u003eembedInProcess()\u003c/code\u003e (cache, never Close); \u003ccode\u003eserve()\u003c/code\u003e (daemon) uses the shared model without closing; \u003ccode\u003eanntool.go\u003c/code\u003e \u003ccode\u003eannExtractEmbed\u003c/code\u003e drops its private \u003ccode\u003esync.Once\u003c/code\u003e for the shared cache\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003emodel.go\u003c/code\u003e \u003ccode\u003eStaticModel.Close()\u003c/code\u003e now releases \u003ccode\u003emat\u003c/code\u003e too (one-shot CLI callers); shared instance is never closed\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epkg/httpapi\u003c/code\u003e pprof binds \u003ccode\u003e127.0.0.1\u003c/code\u003e instead of the API host (was \u003ccode\u003e0.0.0.0:1707\u003c/code\u003e via KB_HOST)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ecompose.yaml\u003c/code\u003e: \u003ccode\u003eKB_BUFFER_POOL\u003c/code\u003e 4 GiB → 512 MiB, \u003ccode\u003edeploy.resources.limits.memory: 2g\u003c/code\u003e (container had \u003ccode\u003eHostConfig.Memory=0\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/brain/embedmodel_test.go\u003c/code\u003e — 200 sequential search embeds load once and never Close; real \u003ccode\u003eembedQuery\u003c/code\u003e fallback loads once; \u003ccode\u003eClose\u003c/code\u003e frees the matrix; plus the #110 cache tests\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eLive before/after (same host, \u003ccode\u003eKBSEARCH_NO_DAEMON=1\u003c/code\u003e, sequential\n\u003ccode\u003etest/stress --mix search:1 --c 1\u003c/code\u003e): before, 13 searches grew RSS 147 MiB →\n6.8 GiB (swap 0.5 GiB) with \u003ccode\u003ealloc_space\u003c/code\u003e \u003ccode\u003eloadMatrix\u003c/code\u003e 13.84 GiB; after, 49,448\nsearches kept \u003ccode\u003ealloc_space\u003c/code\u003e \u003ccode\u003eloadMatrix\u003c/code\u003e at 977 MB (one load), p50 6 ms vs\n1429 ms, RSS post-warmup delta ~36 MiB (\u0026lt; 50 MiB), swap flat. See \u003ccode\u003ePERF.md\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--concurrency-audit-mailsync-corpuswatch-index-bench-gitea-94\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)\" href=\"#2026-08-23--concurrency-audit-mailsync-corpuswatch-index-bench-gitea-94\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAudit of the concurrency paths outside \u003ccode\u003einternal/brain\u003c/code\u003e db/conn lifecycle\n(#109/#110 \u003ccode\u003ebrainMu\u003c/code\u003e), driving the \u003ccode\u003einternal/mailsync\u003c/code\u003e worker pools, the\n\u003ccode\u003ecorpuswatch\u003c/code\u003e poll loop, the \u003ccode\u003ebin/brain/index.go\u003c/code\u003e bulk rebuild and the\n\u003ccode\u003ebin/reasoner/bench.go\u003c/code\u003e bake-off. Goal: data races, unbounded concurrency,\nunsynchronized shared state, goroutine leaks — hardening only, no semantic\nchange. Result per component (all verified with \u003ccode\u003ego test -race\u003c/code\u003e):\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eComponent\u003c/th\u003e\n\u003cth\u003eVerdict\u003c/th\u003e\n\u003cth\u003eHazard(s) found\u003c/th\u003e\n\u003cth\u003eFix\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/mailsync\u003c/code\u003e \u003ccode\u003eRun\u003c/code\u003e worker pool + gmail/m365/onlyoffice sources\u003c/td\u003e\n\u003ctd\u003eclean\u003c/td\u003e\n\u003ctd\u003enone — counters already atomic, failures slice already under mutex, m365 token already channel-mutexed, bounded workers, no leak\u003c/td\u003e\n\u003ctd\u003enone (test seam + race test only)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/corpuswatch\u003c/code\u003e poll loop\u003c/td\u003e\n\u003ctd\u003eclean\u003c/td\u003e\n\u003ctd\u003enone — single-goroutine loop, \u003ccode\u003eStamp\u003c/code\u003e is pure filesystem walk, no shared state\u003c/td\u003e\n\u003ctd\u003enone (race test for \u003ccode\u003eStamp\u003c/code\u003e concurrency)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/brain/index.go\u003c/code\u003e → \u003ccode\u003eWriteCorpus\u003c/code\u003e → \u003ccode\u003eparallelEmbed\u003c/code\u003e + \u003ccode\u003eProgressReporter\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cstrong\u003ehazard fixed\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003eProgressReporter.total\u003c/code\u003e was a plain \u003ccode\u003eint\u003c/code\u003e written from embedding worker goroutines (\u003ccode\u003eReport\u003c/code\u003e) and read under the mutex in \u003ccode\u003erender\u003c/code\u003e → data race\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etotal\u003c/code\u003e → \u003ccode\u003eatomic.Int64\u003c/code\u003e (\u003ccode\u003einternal/brain/corpus_pool.go\u003c/code\u003e)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/reasoner/bench.go\u003c/code\u003e → \u003ccode\u003einternal/reasoner\u003c/code\u003e \u003ccode\u003eClient.Run\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eclean\u003c/td\u003e\n\u003ctd\u003enone — client holds only \u003ccode\u003e*http.Client\u003c/code\u003e + immutable \u003ccode\u003eConfig\u003c/code\u003e, \u003ccode\u003eRun\u003c/code\u003e keeps accounting in call-local state\u003c/td\u003e\n\u003ctd\u003enone (race test for concurrent bench runs)\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/corpus_pool.go\u003c/code\u003e: \u003ccode\u003eProgressReporter.total int\u003c/code\u003e → \u003ccode\u003eatomic.Int64\u003c/code\u003e (race on concurrent \u003ccode\u003eReport\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/brain/progress_race_test.go\u003c/code\u003e — 16-goroutine concurrent \u003ccode\u003eReport\u003c/code\u003e + \u003ccode\u003eFinish\u003c/code\u003e under \u003ccode\u003e-race\u003c/code\u003e; total converges\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/mailsync/sync_race_test.go\u003c/code\u003e — 8-worker \u003ccode\u003eRun\u003c/code\u003e over a fake \u003ccode\u003eSource\u003c/code\u003e under \u003ccode\u003e-race\u003c/code\u003e (test-only \u003ccode\u003esources\u003c/code\u003e seam, no prod behavior change)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/corpuswatch/stamp_race_test.go\u003c/code\u003e — 16-goroutine concurrent \u003ccode\u003eStamp\u003c/code\u003e under \u003ccode\u003e-race\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/reasoner/client_race_test.go\u003c/code\u003e — 12 concurrent \u003ccode\u003eClient.Run\u003c/code\u003e + 16×10 shared-client \u003ccode\u003eRunPrompt\u003c/code\u003e under \u003ccode\u003e-race\u003c/code\u003e vs httptest\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test -race -count=1 ./internal/...\u003c/code\u003e green; \u003ccode\u003ego vet ./internal/...\u003c/code\u003e\nclean; touched files gofmt-clean; cgo Zig build of \u003ccode\u003ebin/brain/search.go\u003c/code\u003e\n(\u003ccode\u003e-tags system_ladybug\u003c/code\u003e) green. Branch \u003ccode\u003eaudit/concurrency#94\u003c/code\u003e off \u003ccode\u003erelease/v1\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--url-адресация-контента-селекторы-гранулярный-сплит-gitea-100\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)\" href=\"#2026-08-23--url-адресация-контента-селекторы-гранулярный-сплит-gitea-100\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: stable content addressing for the ETL graph. Every extracted node gets a\ncanonical URL \u003ccode\u003escheme://platform/thread/msg/path-segments[#anchor]\u003c/code\u003e, node ID =\n\u003ccode\u003esha256(url)[:16]\u003c/code\u003e, separate content ID = \u003ccode\u003esha256(body)\u003c/code\u003e; parts reference each\nother only via URLs. Content splits granularly BEFORE insertion into typed\nItems (paragraph/heading/table/row/cell/image/link/page); a DOM/jQuery-like\nselector mini-language addresses any leaf. Scope: addressing + selector +\nsplit helpers with proof-of-use tests. Graph wiring (#99/#101, Ladybug) and\nformat coverage beyond html/md are out of scope here.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/address\u003c/code\u003e: \u003ccode\u003eSegment\u003c/code\u003e (parse/render \u003ccode\u003etype[index]\u003c/code\u003e), \u003ccode\u003eNew\u003c/code\u003e/\u003ccode\u003eParse\u003c/code\u003e canonical URL (validated scheme/platform/thread/msg/segments/anchor), \u003ccode\u003eNodeID(url)=sha256(url)[:16]\u003c/code\u003e (32 hex), \u003ccode\u003eContentID(body)=sha256\u003c/code\u003e (64 hex)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/selector\u003c/code\u003e: mini-language \u003ccode\u003ep[3] \u0026gt; table[0] \u0026gt; tr[1] td[2]\u003c/code\u003e (\u003ccode\u003e\u0026gt;\u003c/code\u003e direct child, space descendant, optional index = any) — \u003ccode\u003eParse\u003c/code\u003e (strict grammar errors) + \u003ccode\u003eApply(root)\u003c/code\u003e over a typed Item tree\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/items\u003c/code\u003e: \u003ccode\u003eKind\u003c/code\u003e named int enum (page→\u003ccode\u003ebody\u003c/code\u003e/heading/p/table/tr/td/img/a), typed \u003ccode\u003eItem{Kind,Seg,Path,URL,Body,Src,Alt,Href,Children}\u003c/code\u003e; \u003ccode\u003eSplitHTML\u003c/code\u003e (via \u003ccode\u003egolang.org/x/net/html\u003c/code\u003e) and \u003ccode\u003eSplitMarkdown\u003c/code\u003e (pipe-tables + standalone link/image) assign per-type indices and canonical URLs; content split happens BEFORE insertion\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003einternal/address\u003c/code\u003e round-trips + validation + id derivation; \u003ccode\u003einternal/selector\u003c/code\u003e parse/apply + edge cases (bad grammar, missing index, no-match); \u003ccode\u003einternal/items\u003c/code\u003e html/md fixtures (testdata/); \u003ccode\u003einternal/items/e2e_test.go\u003c/code\u003e — split→select→verify URL+node/content id, parsed back to same leaf\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test -race -count=1 ./internal/...\u003c/code\u003e green; \u003ccode\u003ego vet ./internal/...\u003c/code\u003e\nclean; new packages gofmt-clean. Branch \u003ccode\u003efeat/content-url-addressing#100\u003c/code\u003e off\n\u003ccode\u003erelease/v1\u003c/code\u003e. No end-to-end graph write (that's #99/#101 + Ladybug).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — secret-scan в CI всех репо (gitea #142)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--secret-scan-в-ci-всех-репо-gitea-142\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — secret-scan в CI всех репо (gitea #142)\" href=\"#2026-08-23--secret-scan-в-ci-всех-репо-gitea-142\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: не допустить новых утечек секретов на этапе PR/push (после #140 — 8 HIGH-файлов в matrix).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eВыбор инструмента: \u003cstrong\u003egitleaks\u003c/strong\u003e (официальный образ \u003ccode\u003ezricethezav/gitleaks\u003c/code\u003e) — diff/commit-скан, fail-on-leak, generic+private-key правила\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCI job \u003ccode\u003esecret-scan\u003c/code\u003e (pull_request + push, сканирует только diff новых коммитов) в 2dph (ci.yml), go-onlyoffice (test.yml), go-config (secret-scan.yml), matrix (.gitea/workflows/secret-scan.yml)\u003c/td\u003e\n\u003ctd\u003edone — PR 2dph#143, go-onlyoffice#5, go-config#1, matrix#6\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003egit pre-push + pre-commit хуки \u003ccode\u003escripts/githooks/\u003c/code\u003e (+ install.sh), блокируют push/commit при находке\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eСкилы git-workflow + devops: правило «скан перед любым push»; синхронизированы в ai-fabric (release/v2)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eВерификация: скан ловит реальные утечки matrix #140 (14 findings, exit 1); pre-push блокирует push с секретом\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-23--conversation-канон-модель--диск--манифест--граф-gitea-99-epic-88\" class=\"anchor\" aria-label=\"Permalink: 2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)\" href=\"#2026-08-23--conversation-канон-модель--диск--манифест--граф-gitea-99-epic-88\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: единая каноническая модель для почты и чатов — \u003ccode\u003eMessage{ID,ThreadID,Platform, From,ReplyTo,To,CC,BCC,SentAt,Body,Attachments(lazy)}\u003c/code\u003e; хранение на диске под\n\u003ccode\u003evar/corpus/{mail,chats}\u003c/code\u003e как JSON + манифест sha256 (идемпотентный upsert); граф\n\u003ccode\u003e(:Person)-[:SENT]-\u0026gt;(:Message)-[:TO|CC|BCC]-\u0026gt;(:Person)\u003c/code\u003e, \u003ccode\u003e[:REPLY_TO]\u003c/code\u003e, \u003ccode\u003e[:PART_OF]\u003c/code\u003e\nпараграфы. Первый mergeable инкремент: ядро схемы + диск + манифест + round-trip\nи golden-тест (один \u003ccode\u003e.eml\u003c/code\u003e и один telegram-экспорт дают одинаковую схему графа).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/canon\u003c/code\u003e (evidence-слой, пакет-домен): \u003ccode\u003ePerson\u003c/code\u003e/\u003ccode\u003eAttachment\u003c/code\u003e(lazy \u003ccode\u003eOpen(ctx)\u003c/code\u003e, без буферизации тела)/\u003ccode\u003eMessage\u003c/code\u003e/\u003ccode\u003eEdge\u003c/code\u003e/\u003ccode\u003eEdgeType\u003c/code\u003e (SENT/TO/CC/BCC/REPLY_TO/PART_OF), \u003ccode\u003eEdges()\u003c/code\u003e, \u003ccode\u003eGraphSchema()\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eStore{root}\u003c/code\u003e: \u003ccode\u003eWrite\u003c/code\u003e (идемпотентный upsert по sha256 тела), \u003ccode\u003eRead\u003c/code\u003e (скипает битые файлы), \u003ccode\u003eLoadManifest\u003c/code\u003e/\u003ccode\u003eSaveManifest\u003c/code\u003e (атомарный); layout \u003ccode\u003eroot/\u0026lt;platform\u0026gt;/\u0026lt;thread\u0026gt;/\u0026lt;id\u0026gt;.json\u003c/code\u003e; сегменты путей через \u003ccode\u003epkg/utils.SafeSegment\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eFromMail(r)\u003c/code\u003e на emersion/go-message: envelope (From/To/Cc/Bcc через mail.Header.AddressList), ThreadID из References/Message-Id, ReplyTo из In-Reply-To, тело text/plain (fallback text/html → mailconv.StripHTML), вложения lazy (метаданные)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eFromChat(platform,thread,text,from,ts,replyTo)\u003c/code\u003e: Person id = \u003ccode\u003eplatform:handle\u003c/code\u003e, парсинг ts (RFC3339), ReplyTo-цепочка\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD (offline, фикстуры synthetic Alice/Bob/example.com): round-trip write→read→canon для mail и chat; идемпотентный повторный write; манифест = sha256(JSON); layout; Edges/схема; golden — mail и telegram дают одинаковую \u003ccode\u003eGraphSchema()\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone — 13 тестов, \u003ccode\u003e-race\u003c/code\u003e зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003edocs/PLAN.md: секция #99 (этот блок)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eОсталось (вне первого инкремента): конверторы существующих источников (gmail/onlyoffice\n\u003ccode\u003emailconv.Message\u003c/code\u003e, \u003ccode\u003emailsync.Message\u003c/code\u003e, whatsapp/linkedin экспорт) → канон; запись графа\nв Ladybug (\u003ccode\u003e(:Person)/:Message/:Paragraph\u003c/code\u003e узлы + \u003ccode\u003eSENT|TO|CC|BCC|REPLY_TO|PART_OF\u003c/code\u003e\nрёбра) — будет отдельным инкрементом на #99; миграция \u003ccode\u003ebin/*/import\u003c/code\u003e на канон.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego build ./...\u003c/code\u003e green; \u003ccode\u003ego vet ./internal/canon/... ./pkg/utils/...\u003c/code\u003e\nclean; \u003ccode\u003ego test -race ./internal/canon/... ./pkg/utils/...\u003c/code\u003e green. Branch\n\u003ccode\u003efeat/conversation-canon#99\u003c/code\u003e off \u003ccode\u003erelease/v1\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--oo-mail-html-шаблон--render-check-до-отправки-gitea-76\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)\" href=\"#2026-08-25--oo-mail-html-шаблон--render-check-до-отправки-gitea-76\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: из wave-1 письма уходили plain-text одной строкой; теперь брендированный\nHTML-шаблон + гейт рендер-проверки, блокирующий send, пока черновик не пройдёт\nassert (абзацы, подпись, logo cid, chat-line без дублей).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/oohtml\u003c/code\u003e (новый пакет-домен): брендированный шаблон produktor.io (палитра #76: cream \u003ccode\u003e#FAF5EA\u003c/code\u003e, ink \u003ccode\u003e#0A0A0A\u003c/code\u003e, navy \u003ccode\u003e#142039\u003c/code\u003e/\u003ccode\u003e#0E1626\u003c/code\u003e, CTA \u003ccode\u003e#F2C849\u003c/code\u003e, primary \u003ccode\u003e#143A6F\u003c/code\u003e; system-ui; inline-стили; логотип \u003ccode\u003ecid:produktor-logo.gif\u003c/code\u003e без remote-URL)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eRenderCheck(html, TemplateData)\u003c/code\u003e — типизированный ассерт: paragraphs ≥2, greeting, offer+CTA, signature+site, logo cid, отсутствие дублей текстовых блоков (\u003ccode\u003eCode\u003c/code\u003e enum)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eMailer.Send(ctx, SendParams)\u003c/code\u003e — TDD-гейт: \u003ccode\u003eBuild\u003c/code\u003e → \u003ccode\u003eSaveMailDraft\u003c/code\u003e(HTML) → \u003ccode\u003eGetMailMessage\u003c/code\u003e back → \u003ccode\u003eRenderCheck\u003c/code\u003e → \u003ccode\u003eSendMail\u003c/code\u003e только при чистой проверке; иначе error «render check failed, send blocked»\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eBuildMessage(w, …)\u003c/code\u003e — MIME \u003ccode\u003emultipart/related\u003c/code\u003e через \u003ccode\u003eemersion/go-message\u003c/code\u003e: HTML-часть + inline \u003ccode\u003eimage/gif\u003c/code\u003e с \u003ccode\u003eContent-ID\u003c/code\u003e = логотип (cid-embedding, не remote fetch)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eЛого-ассеты \u003ccode\u003eetc/onlyoffice/assets/produktor-logo.{png,gif,-64.gif}\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eготовы\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD offline (фикстуры synthetic Alice/Bob/example.com; fakeStore вместо сети): 10 тестов, \u003ccode\u003e-race\u003c/code\u003e зелёные\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego vet ./...\u003c/code\u003e clean; \u003ccode\u003ego test -race ./internal/oohtml/\u003c/code\u003e green (10/10).\nBranch \u003ccode\u003efeat/ooo-mail-html-template#76\u003c/code\u003e off \u003ccode\u003erelease/v1\u003c/code\u003e. Осталось вне объёма:\nwiring в OnlyOffice CLI (\u003ccode\u003eoo mails send\u003c/code\u003e) и фактическая отправка с вложенным\ncid-логотипом через драфт-API OnlyOffice.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — CRM association two-source rule (gitea #55, epic #52)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--crm-association-two-source-rule-gitea-55-epic-52\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — CRM association two-source rule (gitea #55, epic #52)\" href=\"#2026-08-25--crm-association-two-source-rule-gitea-55-epic-52\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: \u003ccode\u003ebin/facts/prove-crm\u003c/code\u003e (issue references it as \u003ccode\u003ebin/facts/crm\u003c/code\u003e) должен\nдоказывать person→company И company→project как \u003ccode\u003eroot=facts\u003c/code\u003e только когда оба\nисточника (corpus SoT \u003ccode\u003eeslider/cv/projects/knowledge-mesh-seed.yaml\u003c/code\u003e × ooCRM\n\u003ccode\u003egraph.json\u003c/code\u003e) согласны; односторонние — mismatch, никогда fact.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003emerge-правило вынесено из \u003ccode\u003emain()\u003c/code\u003e в \u003ccode\u003einternal/facts.CRMAssocFacts\u003c/code\u003e (single implementation, rule #10): person→company (companies_with_persons) + company→project (projects_contacts)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eфикс ложного совпадения: generic-токены (\u003ccode\u003eclient\u003c/code\u003e, \u003ccode\u003egmbh\u003c/code\u003e, \u003ccode\u003eag\u003c/code\u003e, \u003ccode\u003esystems\u003c/code\u003e, \u003ccode\u003eplatform\u003c/code\u003e, …) больше не двигают company-match (\u003ccode\u003esignificantTokens\u003c/code\u003e stopwords) — «Markets Platform (client)» больше не матчит «Client not named yet»\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eдедуп mismatches (раньше employer-kind loop дублировал «not found») + отчёт CRM-only компаний и проектов без company\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD offline на synthetic-фикстурах: two-source rule + все mismatch-пути + generic-token регрессия, \u003ccode\u003e-race\u003c/code\u003e зелёные\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test -race ./internal/facts/\u003c/code\u003e green (тесты crm/company-merge).\nПротив реального SoT (dry-run): proven=0; 9 corpus-org не в CRM; 4 CRM-only\nкомпании (Capitole, Client not named yet, Teksystems, talabat); 198 проектов\nбез company — \u003ccode\u003eprojects_contacts[].companies\u003c/code\u003e в \u003ccode\u003egraph.json\u003c/code\u003e пуст у всех\n(данные заполняет внешний \u003ccode\u003egraph.py\u003c/code\u003e, вне репо). company→project пока недоказуем\nиз-за этого data-gap; person→company недоказуем — CRM-компании с персонами не\nсовпадают с corpus orgs. Branch \u003ccode\u003efeat/crm-assoc#55\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--facts-lookup--contradiction-as-not-confirmed-test-plan-gitea-58-epic-52\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)\" href=\"#2026-08-25--facts-lookup--contradiction-as-not-confirmed-test-plan-gitea-58-epic-52\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: prove the \u003ccode\u003esearch → get → audit\u003c/code\u003e operator flow on ONE known deal and the\ncontradiction-as-\u003ccode\u003e(not confirmed)\u003c/code\u003e path (D16), offline with synthetic fixtures.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eoffline test: \u003ccode\u003esearch\u003c/code\u003e (fuse FTS+vector, root filter) → \u003ccode\u003eget\u003c/code\u003e (leaf by id → source/confidence) → \u003ccode\u003eaudit\u003c/code\u003e (\u003ccode\u003efacts.CheckFactRow\u003c/code\u003e + \u003ccode\u003efacts.Adjudicate\u003c/code\u003e) confirms two-source facts on synthetic deal \u003ccode\u003eacme-2026\u003c/code\u003e (Alice/Bob/example.com)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eoffline test: 2v2 contradiction leaf (\u003ccode\u003ea x b vs c x d\u003c/code\u003e, no rule fires) stays \u003ccode\u003e(not confirmed)\u003c/code\u003e/hypothesis until audited; never a confirmed fact; deduction escalates\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eoperator flow documented for bot/assistant (\u003ccode\u003esearch\u003c/code\u003e/\u003ccode\u003eget\u003c/code\u003e/\u003ccode\u003eaudit\u003c/code\u003e; confirmed vs \u003ccode\u003e(not confirmed)\u003c/code\u003e) — \u003ccode\u003edocs/facts/audit-recipes.md\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego vet ./internal/facts/... ./internal/brain/rank/...\u003c/code\u003e clean;\n\u003ccode\u003ego test -race ./internal/brain/rank/ ./internal/facts/\u003c/code\u003e green\n(\u003ccode\u003eTestDealSearchGetAuditConfirmedFlow\u003c/code\u003e, \u003ccode\u003eTestDealContradictionNotConfirmedUntilAudited\u003c/code\u003e).\nБлокер #53 закрыт; live \u003ccode\u003eget\u003c/code\u003e против \u003ccode\u003evar/kb.lbug\u003c/code\u003e + OnlyOffice CRM остаётся\nвне объёма (offline-план доказывает cgo-free логику). Branch \u003ccode\u003etest/facts-lookup#58\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--facts-root-ingestion-repair-empty-facts-layer-gitea-181\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)\" href=\"#2026-08-25--facts-root-ingestion-repair-empty-facts-layer-gitea-181\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: the production brain had \u003cstrong\u003e0 facts-root\u003c/strong\u003e leafs — all 9204 in \u003ccode\u003einfo\u003c/code\u003e\n(9201 confirmed). Root cause: \u003ccode\u003ebin/brain/index.go --rebuild\u003c/code\u003e was run without\n\u003ccode\u003e--with-facts\u003c/code\u003e; \u003ccode\u003einternal/brain/corpus.go\u003c/code\u003e writes every corpus leaf as\n\u003ccode\u003eroot=info\u003c/code\u003e (single file source), and the 2-source facts layer is a separate\n\u003ccode\u003ebin/facts/extract.go\u003c/code\u003e stage. Audit: all 9201 confirmed info leafs are\nsingle-source corpus leafs → \u003cstrong\u003e0\u003c/strong\u003e qualify for re-rooting by the ≥2-sources rule.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain\u003c/code\u003e facts-promotion core: \u003ccode\u003eSourceCount\u003c/code\u003e (source string, \u003ccode\u003e\" x \"\u003c/code\u003e separator), \u003ccode\u003ePromoteEligible\u003c/code\u003e (≥2 sources + confirmed → facts), \u003ccode\u003ePromoteFacts\u003c/code\u003e/\u003ccode\u003eEligibleInfoLeafs\u003c/code\u003e (DB scan, idempotent upsert)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD tests: \u003ccode\u003eTestSourceCount\u003c/code\u003e, \u003ccode\u003eTestPromoteEligible\u003c/code\u003e, \u003ccode\u003eTestPromoteFactsPromotesAndIdempotent\u003c/code\u003e, \u003ccode\u003eTestPromoteFactsReadPathDistinguishes\u003c/code\u003e — \u003ccode\u003ego test -race ./internal/brain/\u003c/code\u003e green\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/facts/promote.go\u003c/code\u003e CLI (\u003ccode\u003e--dry-run\u003c/code\u003e, \u003ccode\u003e--db\u003c/code\u003e); \u003ccode\u003ebin/facts/extract.go --dry-run\u003c/code\u003e = 18 two-source facts\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003elive DB: writes blocked (var/ owned 1001, no sudo; in-service \u003ccode\u003e/ingest\u003c/code\u003e bulk write hung the C layer → container restart). Full ingestion demonstrated on a faithful copy: before facts=0 info=9204 → after facts=18 info=9204\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003edocs: \u003ccode\u003edocs/facts/ingest.md\u003c/code\u003e procedure + live runbook; \u003ccode\u003edocs/brain/rebuild.md\u003c/code\u003e facts note\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego vet ./internal/brain/ ./bin/facts/\u003c/code\u003e clean;\n\u003ccode\u003ego test -race ./internal/brain/ -run 'TestSourceCount|TestPromoteEligible|TestPromoteFacts'\u003c/code\u003e green.\nLive write to \u003ccode\u003evar/kb.lbug\u003c/code\u003e requires the brain stopped + DB-owner (uid 1001/root)\nwrite access — procedure in \u003ccode\u003edocs/facts/ingest.md\u003c/code\u003e. Branch \u003ccode\u003efeat/facts-ingest#181\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--импорт-гэп-почтыконтактов-с-mnt8tb-gitea-79\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)\" href=\"#2026-08-25--импорт-гэп-почтыконтактов-с-mnt8tb-gitea-79\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: закрыть гэп импорта дисковых источников почты/контактов на /mnt/8TB,\nкоторые не были в пайплайне (#68/#74 импортировали только var/mail). Wave-1\n(#84) уже импортировал mbox/.eml: tb-andriy-profile 35,368 · tb-backup-128g\n39,435 · tb-2010-zip 1,289 · contacts-eml 125 (Krylov исключён). Этот шаг —\nвынести mbox-сплиттер в тестируемый слой + доказать импорт тестами.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eинвентарь источников подтверждён на диске (read-only): TB-профиль Mail/ImapMail, оба PST (265 KB), Backup E-Mails, zip-2010, VCF/MAB — таблица-истина в \u003ccode\u003edocs/mail-sources.md\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003embox-сплиттер вынесен из build-tag скрипта в \u003ccode\u003einternal/mailconv\u003c/code\u003e (single implementation): \u003ccode\u003eSplitMbox\u003c/code\u003e, \u003ccode\u003eSplitMboxDir\u003c/code\u003e, \u003ccode\u003eMboxMessage\u003c/code\u003e, \u003ccode\u003eLooksMbox\u003c/code\u003e; \u003ccode\u003ebin/mail/convert-mbox.go\u003c/code\u003e — тонкая обёртка\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003eTestSplitMbox\u003c/code\u003e, \u003ccode\u003eTestMboxMessage\u003c/code\u003e (mbox→Message + source-тег), \u003ccode\u003eTestSplitMboxDirIdempotent\u003c/code\u003e (повторный прогон без дублей, контент-адресация sha256:16), \u003ccode\u003eTestFromEMLFlatRoot\u003c/code\u003e (contacts-eml плоский root) — \u003ccode\u003ego test -race ./internal/mailconv/\u003c/code\u003e зелёные\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePST: \u003ccode\u003ereadpst\u003c/code\u003e НЕ установлен → источник остаётся ЗАБЛОКИРОВАН; путь закрытия (libpst-utils → readpst -e → .eml → FromEML) задокументирован, не подделан\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003edocs: \u003ccode\u003edocs/mail-sources.md\u003c/code\u003e (таблица-истина), runbook-секция, конфиг/abs-путей в коммитах нет\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego vet ./internal/mailconv/ ./bin/mail/\u003c/code\u003e clean;\n\u003ccode\u003ego test -race ./internal/mailconv/ ./internal/source/ ./internal/mailsync/ ./internal/canon/ ./pkg/contact/\u003c/code\u003e green. Исключения политики (#79): Krylov,\nDrafts/Templates/Trash/Junk/Spam/Unsent, *.msf. Mail-send путь не трогали.\nBranch \u003ccode\u003efeat/mail-import-gap#79\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--docsskills-синк-под-текущий-тулзверь-агентов-gitea-81\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)\" href=\"#2026-08-25--docsskills-синк-под-текущий-тулзверь-агентов-gitea-81\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: свежий агент по AGENTS.md делает sync-волну, reconcile report,\ninteraction write — без вопросов в чат. Git ops truth: git = HTTPS+token,\ntea CLI для issues/PR (SSH deploy-key :222 на write сломан, #73 closed\nobsoleted-by).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eAGENTS.md: git-ops truth (#81/#73), go-onlyoffice split «примитивы→lib, флоу→2dph», Tools-блок с sync-волной + reconcile + interaction, source map #79 + exclusion policy\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eskills: \u003ccode\u003eapi-client\u003c/code\u003e — go-onlyoffice split; \u003ccode\u003eetl-handler\u003c/code\u003e — sync-волна + OO-флоу\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003edocs/runbook.md\u003c/code\u003e — секция «Sync wave + OO CRM» с verified-командами\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003everify: sync \u003ccode\u003e--dry-run\u003c/code\u003e печатает волну; reconcile/import-interaction \u003ccode\u003e--help\u003c/code\u003e и корпус-скан работают; OO-запись — только с env-кредами\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego run ./bin/stack/sync.go --dry-run\u003c/code\u003e ok;\n\u003ccode\u003ego run -tags=onlyoffice_reconcile_contact ./bin/onlyoffice/reconcile-contact.go --help\u003c/code\u003e\nи \u003ccode\u003ego run -tags=onlyoffice_import_interaction ./bin/onlyoffice/import-interaction.go --help\u003c/code\u003e\nok (флаги \u003ccode\u003e--sources/--write/--limit\u003c/code\u003e совпадают с докой). Найдено при\nверификации: \u003ccode\u003e--only\u003c/code\u003e в \u003ccode\u003ebin/stack/sync.go\u003c/code\u003e принимает имена шагов\n(\u003ccode\u003email,mail-import,chats,contact-brain,git-brain,contact-crm\u003c/code\u003e), а не логические\nгруппы — хелп-строка флага это врёт; документировано как есть, баг — на PO\n(#81-коммент). Branch \u003ccode\u003edocs/skills-sync#81\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--hnsw-leaf_vec-краш--in-process-cosine-scan-gitea-192\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)\" href=\"#2026-08-25--hnsw-leaf_vec-краш--in-process-cosine-scan-gitea-192\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: liblbug 0.19.0 SIGSEGV на вставке в HNSW Leaf_vec (NULL deref в\nsimsimd_cos_f32, OnDiskHNSWIndex::shrinkForNode) при графе \u0026gt;~1300 нод — наш\nканон с ~28k leafs был под крашом (rc=139, тихий рестарт). Порт фикса mdx-1\n(medex/brain-stack 6e0f379). Bug A (close/reopen race) у нас уже закрыт #109.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003erank/query.go\u003c/code\u003e: VecStmt удалён (комментарий с rationale)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003esearch.go\u003c/code\u003e: queryVector → brute-force cosine scan по \u003ccode\u003el.embedding\u003c/code\u003e (vecScanStmt), cosineToQuery + sqrt64; импорты math/sort\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ewrite.go\u003c/code\u003e: EnsureIndexes создаёт только FTS \u003ccode\u003eid\u003c/code\u003e; Leaf_vec не создаётся/не требуется, существующий не дропается\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003evector_test.go\u003c/code\u003e — cosine known-vectors, zero-norm → 0 (не NaN), float64-элементы, truncation\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ezig go test -tags system_ladybug ./internal/brain/\u003c/code\u003e green (кроме пре-существующих modeldir env-failures), vet clean, gofmt clean\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ereindex live kb.lbug (\u003ccode\u003ebin/brain/index.go --rebuild\u003c/code\u003e) — ждёт PO-подтверждения\u003c/td\u003e\n\u003ctd\u003epending\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePLAN.md блок\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ebin/cgo/zig go test -tags system_ladybug -skip TestModelDir ./internal/brain/\u003c/code\u003e ok; \u003ccode\u003ego test ./internal/brain/rank/\u003c/code\u003e ok. Пре-существующие\nпадения \u003ccode\u003eTestModelDir*\u003c/code\u003e (HF-модель уже локально в models/, окружение) — не\nсвязаны с этим изменением. Branch \u003ccode\u003efix/hnsw-cosine-scan#192\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-26 — весь mail-корпус в brain (gitea #199)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-26--весь-mail-корпус-в-brain-gitea-199\" class=\"anchor\" aria-label=\"Permalink: 2026-08-26 — весь mail-корпус в brain (gitea #199)\" href=\"#2026-08-26--весь-mail-корпус-в-brain-gitea-199\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: оба корпуса — live \u003ccode\u003evar/corpus/mail\u003c/code\u003e и legacy \u003ccode\u003evar/mail\u003c/code\u003e (#184) — в\nbrain; шаг волны \u003ccode\u003email-index\u003c/code\u003e. Решение B: \u003ccode\u003eLoadMailLeafs\u003c/code\u003e расширен на\nнесколько root-ов, дедуп по sha256 content-address (одинаковое письмо в\nобоих корпусах → один leaf), без копирования данных.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/brain/mail_corpus.go\u003c/code\u003e: \u003ccode\u003eMailRoots\u003c/code\u003e + \u003ccode\u003eLoadMailLeafs\u003c/code\u003e multi-root + dedup; вынесен из \u003ccode\u003ecorpus.go\u003c/code\u003e (cgo-free)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/brain/index.go --with-mail\u003c/code\u003e → \u003ccode\u003ebrain.MailRoots(root)\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/stack/sync.go\u003c/code\u003e: шаг \u003ccode\u003email-index\u003c/code\u003e (после mail-import, \u003ccode\u003e--with-mail\u003c/code\u003e), brain runner с Zig CGO-тегами \u003ccode\u003ebrain_index\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: \u003ccode\u003email_corpus_test.go\u003c/code\u003e (multi-root, dedup, since, limit, attachment) + \u003ccode\u003esync_test.go\u003c/code\u003e (mail-index план, runner-теги)\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race ./internal/brain/... ./bin/stack/... ./internal/mailconv/...\u003c/code\u003e green\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eПолный индекс \u003ccode\u003eindex.go --skip --with-mail\u003c/code\u003e в существующую kb.lbug\u003c/td\u003e\n\u003ctd\u003edone — 304 544 leafs, +7 712 новых (прошлый индекс был частичным после corrupt-199); итог info 313 786\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eИдемпотентность (повтор \u003ccode\u003e--skip --with-mail\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003edone — 0 новых\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eSearch реальных писем (e2e-outbound, PST-тема)\u003c/td\u003e\n\u003ctd\u003edone — count ≥ 1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003edocs: AGENTS.md / runbook \u003ccode\u003e--only\u003c/code\u003e список + \u003ccode\u003e--with-mail\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eИзвестное ограничение: векторный поиск — линейный скан (#192), ~32s при 313k leafs\u003c/td\u003e\n\u003ctd\u003eopen — отдельный issue на оптимизацию\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eBranch \u003ccode\u003efeat/mail-corpus-brain#199\u003c/code\u003e, PR #200 → release/v1.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)\u003c/h2\u003e\u003ca id=\"user-content-2026-08-25--импорт-outlook-pst-readpst--e--eml--пайплайн-gitea-185\" class=\"anchor\" aria-label=\"Permalink: 2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)\" href=\"#2026-08-25--импорт-outlook-pst-readpst--e--eml--пайплайн-gitea-185\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: закрыть остаток #79 — два PST-файла (Andriy + копия в бэкапе 128Gb).\n\u003ccode\u003ereadpst -e\u003c/code\u003e → набор \u003ccode\u003e.eml\u003c/code\u003e → существующий \u003ccode\u003emailconv.FromEML\u003c/code\u003e (source-тег \u003ccode\u003epst/\u003c/code\u003e).\nКонвертер по решению PO: readpst из пакета \u003ccode\u003epst-utils\u003c/code\u003e (Ubuntu 24.04; бывший\n\u003ccode\u003elibpst-utils\u003c/code\u003e).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003ereadpst на хост: системная установка \u003ccode\u003eapt-get install pst-utils\u003c/code\u003e заблокирована (нет passwordless sudo) — установлен локально в \u003ccode\u003evar/dist/readpst\u003c/code\u003e (dpkg-deb -x, \u003ccode\u003epst.readpst\u003c/code\u003e в config.local.yml)\u003c/td\u003e\n\u003ctd\u003edone (блокер зафиксирован в issue)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/source/pst.go\u003c/code\u003e: адаптер \u003ccode\u003esource.PST\u003c/code\u003e (#97-паттерн) — \u003ccode\u003ereadpst -e\u003c/code\u003e в вытираемый scratch (\u003ccode\u003evar/tmp/pst/\u0026lt;label\u0026gt;\u003c/code\u003e), политика #79 через \u003ccode\u003emailconv.SkipFolder\u003c/code\u003e (немецкие имена: Entwürfe/Vorlagen/Gelöschte Objekte/Junk-E-Mail/Postausgang), контент-адресный копирование в корпус \u003ccode\u003eOut/\u0026lt;label\u0026gt;/\u0026lt;folder\u0026gt;/\u0026lt;sha256:16\u0026gt;/\u0026lt;sha256:16\u0026gt;.eml\u003c/code\u003e; \u003ccode\u003eImportPST\u003c/code\u003e/\u003ccode\u003ePlanPST\u003c/code\u003e (sync + \u003ccode\u003emailconv.FromEML\u003c/code\u003e), \u003ccode\u003e--dry-run\u003c/code\u003e план\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eнормализация \u003ccode\u003eLibPST-iamunique-\u0026lt;n\u0026gt;\u003c/code\u003e MIME-boundary (readpst вставляет случайный токен на каждом прогоне) — иначе контент-ID нестабильны и повторный прогон плодит дубли\u003c/td\u003e\n\u003ctd\u003edone (найдено на реальных данных, TDD-тест)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/mail/import-pst.go\u003c/code\u003e: тонкая CLI-обёртка (flaggy, конфиг pst.*, ctx-first, идемпотентность, state \u003ccode\u003evar/state/pst.json\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eконфиг: \u003ccode\u003eConfig.PST\u003c/code\u003e + секция \u003ccode\u003epst:\u003c/code\u003e в \u003ccode\u003eetc/brain/config.yml\u003c/code\u003e (sources/readpst/out/state; отсутствие sources = явная ошибка с указанием на конфиг)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD offline (synthetic Alice/Bob/example.com, фейковый readpst через test-seam hook): конверсия .eml→canon (folder=Posteingang), идемпотентный повтор (0 дублей), state-чекпойнт, dry-run, policy-исключения, resolveReadPST (override→PATH→var/dist→ошибка), идентичные архивы (дедуп по seen-set)\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test -race ./internal/source/ ./internal/mailconv/ ./internal/config/\u003c/code\u003e green\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eПрогон реальных PST (оба файла байт-идентичны, sha256 совпадает): 1 уникальное письмо (Outlook-приветствие 2005, \u003ccode\u003ePosteingang\u003c/code\u003e) × 2 лейбла; повторный прогон new=0 skipped=2, дублей нет; readpst извлёк также 78 VCF + 6 ICS (импортируются другими пайплайнами, #68)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003edocs: \u003ccode\u003edocs/mail-sources.md\u003c/code\u003e (статусы PST → импортировано, секция PST-импорт), PLAN.md (этот блок)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego build ./...\u003c/code\u003e (кроме pre-existing build-tag пакетов bin/{reasoner,onlyoffice,postgres} — main без тега), \u003ccode\u003ego vet ./...\u003c/code\u003e, \u003ccode\u003ego test -race ./...\u003c/code\u003e зелёные; gofmt чист (shebang-файлы исключены). Корпус: \u003ccode\u003evar/corpus/mail/pst/{pst-andriy,pst-backup-128g-vorlagen}\u003c/code\u003e + state \u003ccode\u003evar/state/pst.json\u003c/code\u003e (1 seen-id). Branch \u003ccode\u003efeat/pst-import#185\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-02--d-13-импорт-gator-wheregroup--граф-gitea-260-epic-257\" class=\"anchor\" aria-label=\"Permalink: 2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)\" href=\"#2026-09-02--d-13-импорт-gator-wheregroup--граф-gitea-260-epic-257\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: пилот импорта канала \u003cstrong\u003ewheregroup\u003c/strong\u003e (3984 письма, канон gator kind=mail)\nв kb.lbug как Message/Person узлы + рёбра SENT/TO/CC/BCC/REPLY_TO по write-пути\nD-1.2 (#259) и коннектору D-1.1 (#258). Доказать связку «gator канон → граф\n2dph» до gmail (D-1.4 #261). Read path не тронут; сырьё var/mail не читается.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epkg/duckdb\u003c/code\u003e: \u003ccode\u003eExec\u003c/code\u003e/\u003ccode\u003eQueryRows\u003c/code\u003e — произвольный SELECT поверх read_parquet как JSON-совместимые rows (gator-паттерн; LIST(STRUCT)→[]any из map, DECIMAL→float64)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/mailgraph\u003c/code\u003e (cgo-free): типизированный Row (message_id/folder/from/to/cc/bcc/in_reply_to/subject/date/body/content_hash), \u003ccode\u003eMapRow\u003c/code\u003e/\u003ccode\u003eMapRows\u003c/code\u003e (email lowercase, date RFC3339/TIMESTAMP, NULL-списки), \u003ccode\u003eReadSQL\u003c/code\u003e (latest на message_id по observed_at + tombstone deleted исключён + channel), \u003ccode\u003eResolveThreads\u003c/code\u003e (thread_id = корень цепочки in_reply_to, dangling → внешний корень, цикл → fallback), \u003ccode\u003eSortByDate\u003c/code\u003e (родители раньше ответов), \u003ccode\u003eToInputs\u003c/code\u003e → \u003ccode\u003ebrain.MessageInput\u003c/code\u003e, \u003ccode\u003eComputeStats\u003c/code\u003e (folder-раскладка/REPLY_TO/уникальные email)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eконфиг: \u003ccode\u003eConfig.Gator.MailHive\u003c/code\u003e (\u003ccode\u003egator.mailhive\u003c/code\u003e, шаблон etc/brain/config.yml + config.local.yml, флаг \u003ccode\u003e--hive\u003c/code\u003e / env \u003ccode\u003eGATOR_MAIL_HIVE\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/mail/graph.go\u003c/code\u003e (cgo+system_ladybug+mail_graph): \u003ccode\u003e--channel\u003c/code\u003e + dry-run по умолчанию / \u003ccode\u003e--commit\u003c/code\u003e, \u003ccode\u003e--db\u003c/code\u003e, \u003ccode\u003e--skip-existing\u003c/code\u003e (инкремент), live-holder guard (--force), батч-транзакции, отчёт узлы/рёбра после записи\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: юнит cgo-free (MapRow null/регистр/date-форматы, Input/gator_ref \u003ccode\u003ekind=mail#v-\u0026lt;hash8\u0026gt;\u003c/code\u003e, SortByDate, ResolveThreads root/dangling/цикл, Stats порядок-независим, ReadSQL latest/tombstone/экранирование) + pkg/duckdb roundtrip parquet\u003c/td\u003e\n\u003ctd\u003edone — зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ecgo-интеграция (temp Ladybug + synthetic hive parquet): полный цикл → узлы/рёбра, повтор = 0 дублей, \u003ccode\u003e--skip-existing\u003c/code\u003e = 0 новых, dangling REPLY_TO не создаётся\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003eTestImportCycle\u003c/code\u003e (gcc ladybug+duckdb; CI-шаг добавлен, zig duckdb ABI не тянет)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003elive-прогон wheregroup 3984 → \u003ccode\u003evar/kb.lbug\u003c/code\u003e (бэкап \u003ccode\u003ekb.lbug.pre-d13-260.bak\u003c/code\u003e, compose brain остановлен на запись)\u003c/td\u003e\n\u003ctd\u003edone — Message 0→3984, Person 0→167, рёбра SENT 3984 / TO 4855 / CC 302 / BCC 2 / REPLY_TO 639 (после повтора; первый прогон 630 — 9 рёбер досоздались, родитель позже ответа по date)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eповторный прогон идемпотентен; \u003ccode\u003e--skip-existing\u003c/code\u003e = 0 записанных\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eспот-чек: folder INBOX 3143 / Sent 479 / INBOX/Unmatched 362 (сверка с gator), Person \u003ca href=\"mailto:andriy.oblivantsev@wheregroup.com\"\u003eandriy.oblivantsev@wheregroup.com\u003c/a\u003e, gator_ref \u003ccode\u003ekind=mail#v-\u0026lt;hash8\u0026gt;\u003c/code\u003e, REPLY_TO-цепи, thread_id заполнен\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego vet ./internal/...\u003c/code\u003e + \u003ccode\u003ego test -race ./internal/mailgraph/ ./internal/config/ ./pkg/duckdb/ ./internal/canon/\u003c/code\u003e green; cgo \u003ccode\u003ego test -tags system_ladybug ./internal/mailgraph/\u003c/code\u003e green (gcc); CLI dry-run/commit/повтор на live. Branch \u003ccode\u003efeat/graph-import-wheregroup#260\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-03--n-11-фильтр-людикомпании-vs-сервис-аккаунты-для-accept-сетей-gitea-268-epic-267\" class=\"anchor\" aria-label=\"Permalink: 2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)\" href=\"#2026-09-03--n-11-фильтр-людикомпании-vs-сервис-аккаунты-для-accept-сетей-gitea-268-epic-267\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: детерминированный классификатор адреса сети (L-9.5 #234) —\n\u003ccode\u003eperson | company | service\u003c/code\u003e — чтобы CRM-экспорт (\u003ccode\u003e--accept-only\u003c/code\u003e) не тащил\nсервис-аккаунты/подсистемы/рассылки (GitLab/PayPal/LinkedIn/\nmarkets-platform/chiliproject@trac), а реальные люди и компании-контекст\nпроходили. База — sender-эвристики \u003ccode\u003emailconv.IsMachineSender\u003c/code\u003e\n(junkDomains/junkLocalParts). Правила: docs/brain/crm-network-filter.md.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/mailconv/classify.go\u003c/code\u003e (cgo-free): \u003ccode\u003eClassifySender(ParsedAddress)\u003c/code\u003e → kind; каскад: релеи людей (linkedin hit-reply/inmail-hit-reply) → person; junk-домены mailconv (общий \u003ccode\u003eisJunkDomain\u003c/code\u003e, одна реализация) + сервис-домены сети (gitlab/paypal/markets-platform/xing/djinni/wellfound/…); local-автоматы (noreply/mailer-daemon/robot/рассылки/security); tool-имена подсистем (chiliproject/projeqtor/trac/wiki/…) в local и левом лейбле домена; \u003ccode\u003e*-request\u003c/code\u003e/\u003ccode\u003e*-owner\u003c/code\u003e/\u003ccode\u003elists.*\u003c/code\u003e списки; имя-эвристика (Title/ALLCAPS/инициалы/частицы, org/role-слова); fallback first.last@домен; иначе company\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/network\u003c/code\u003e: поле \u003ccode\u003eLink.Kind\u003c/code\u003e в BuildLinks (аддитивно, #234 API не ломает)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebin/network\u003c/code\u003e: \u003ccode\u003e--exclude-services\u003c/code\u003e + \u003ccode\u003e--accept-only\u003c/code\u003e исключает kind=service; kind в text/YAML/JSON\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: юнит классификатора (домены/паттерны/границы noreply@люди, robots.txt в имени, релеи) + сети (Kind проставляется, сервисы не в accept-экспорте)\u003c/td\u003e\n\u003ctd\u003edone — зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eLive-пилот на kb.lbug (read-only): гдеgroup top-60 accept = 44 person / 5 company / 11 service; eslider@ 1240 accept = 317 person / 494 company / 429 service; топ-сервисы eslider@ и chiliproject@trac — service, Astrid Emde/Ruby Rodriguez/Celine Schlindwein — person\u003c/td\u003e\n\u003ctd\u003edone (таблица на #268)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/brain/crm-network-filter.md (правила/границы OPEN) + graph-network.md + PLAN.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test ./internal/mailconv/ ./internal/network/\u003c/code\u003e + \u003ccode\u003ego vet ./...\u003c/code\u003e\ngreen; cgo \u003ccode\u003ebin/cgo/zig go vet -tags system_ladybug,network_cli ./bin/network/\u003c/code\u003e\ngreen. Branch \u003ccode\u003efeat/network-human-filter#268\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-03--n-12-коннектор-oo-импорт-accept-манифеста-сети-в-crm-gitea-269-epic-267\" class=\"anchor\" aria-label=\"Permalink: 2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)\" href=\"#2026-09-03--n-12-коннектор-oo-импорт-accept-манифеста-сети-в-crm-gitea-269-epic-267\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: write-инструмент — манифест accept-связей (bin/network --accept-only,\nnetwork.Manifest) → контакт Person в OnlyOffice CRM (email/имя/тег источника\n\u003ccode\u003e2dph:network:\u0026lt;target\u0026gt;\u003c/code\u003e/about со сводкой + premises), идемпотентно по email;\nсервис-аккаунты (N-1.1 #268) пропущены; dry-run/report-only ничего не пишет.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/network/manifest.go\u003c/code\u003e: тип \u003ccode\u003enetwork.Manifest\u003c/code\u003e (crmDoc/crmLink из bin/network, L-9.5 #234) — общий контракт продюсера и коннектора; bin/network печатает его\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/ooimport\u003c/code\u003e (cgo-free): \u003ccode\u003eParseManifest\u003c/code\u003e → \u003ccode\u003eBuildPlan\u003c/code\u003e (маппинг person/company→Person: имя через mailconv.SplitPersonName, fallback локальная часть email; kind=service/без email → skipped; тег default \u003ccode\u003e2dph:network:\u0026lt;target-email\u0026gt;\u003c/code\u003e; about: \u003ccode\u003e{msgs} писем / {threads} тредов / {replies} ответов, период {period}; premises: mail/commit … (+N ещё); source: \u0026lt;manifest\u0026gt;\u003c/code\u003e) → \u003ccode\u003eRun\u003c/code\u003e (BuildContactEmailIndex один проход → matched skip / create: CreatePerson + AddContactInfo email primary + CreateContactTag/AddContactTag; rollback созданного при сбое email/тега; dry-run/limit)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003ebin/onlyoffice/import-network.go\u003c/code\u003e (shebang, tag onlyoffice_import_network): `--manifest \u0026lt;file\u003c/td\u003e\n\u003ctd\u003e-\u0026gt;\u003ccode\u003e(обязателен),\u003c/code\u003e--dry-run\u003ccode\u003e/\u003c/code\u003e--write\u003ccode\u003e(mutually exclusive),\u003c/code\u003e--limit N\u003ccode\u003e, \u003c/code\u003e--tag`; report only по умолчанию; отчёт created/matched/skipped/failed/pending\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eМаппинг-решения: linkedin-релеи людей (\u003ca href=\"mailto:hit-reply@linkedin.com\"\u003ehit-reply@linkedin.com\u003c/a\u003e, display name реальный) — Person как есть (email=релей, имя=человек); роль-ящики/компании (info@/alle@, kind=company) — Person с fallback-именем (компания-группировка — N-1.4 #271); kind=service не идёт\u003c/td\u003e\n\u003ctd\u003edone (док: docs/brain/oo-network-import.md)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD офлайн: ParseManifest (+roundtrip продюсер-консьюмер yaml), BuildPlan (имя/email lowercase/тег/about-точная строка/sort/skip), splitByExisting (повтор = 0 новых)\u003c/td\u003e\n\u003ctd\u003edone — зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eИнтеграция с живым OO (\u003ccode\u003e//go:build integration\u003c/code\u003e, creds ONLYOFFICE_URL/USER/PASS, без creds — skip): dry-run ничего не пишет → write создаёт контакт email/тег/about → повтор = 0 новых/all matched; --limit создаёт часть, добивание — остальное; тестовый контакт удаляется в t.Cleanup (тег остаётся 0-count — API не удаляет теги)\u003c/td\u003e\n\u003ctd\u003edone — зелёные на office.produktor.io\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/brain/oo-network-import.md (маппинг/команды/границы) + graph-network.md §4 + PLAN.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test ./internal/network/ ./internal/mailconv/ ./internal/ooimport/\u003c/code\u003e\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003ego vet ./...\u003c/code\u003e green; integration \u003ccode\u003ego test -tags=integration ./internal/ooimport/\u003c/code\u003e\ngreen (live OO); CLI dry-run на fixture — report-only, ничего не пишет.\nBranch \u003ccode\u003efeat/oo-crm-connector#269\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-03--n-14-компания-группировка-по-домену--дописывание-тегаabout-matched-gitea-271-epic-267\" class=\"anchor\" aria-label=\"Permalink: 2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)\" href=\"#2026-09-03--n-14-компания-группировка-по-домену--дописывание-тегаabout-matched-gitea-271-epic-267\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: по итогам пилота N-1.3 (#270) довести гдеgroup-импорт до структуры\n«деловой контакт»: (1) role-ящики/компании гдеgroup сгруппировать под\nкомпанией по домену email; (2) существующим matched-контактам (32 из #270,\nв т.ч. топ-5 из #85) \u003cstrong\u003eаддитивно\u003c/strong\u003e дописать тег \u003ccode\u003e2dph:network:\u0026lt;target\u0026gt;\u003c/code\u003e +\nabout-premises-сводку (решение владельца #267); (3) дедуп — только report\n(реальных дублей пилот не выявил, мульти-адреса — YAGNI эпика).\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003einternal/ooimport\u003c/code\u003e: поле \u003ccode\u003eContact.Kind\u003c/code\u003e (класс связи манифеста person/company; service отфильтрован) — основа для группировки\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ecompany.go\u003c/code\u003e: \u003ccode\u003eCompanyRule\u003c/code\u003e (домен→компания), \u003ccode\u003eParseCompanies\u003c/code\u003e (YAML, валидация: домен с точкой, не email, без повторов; сорт), \u003ccode\u003eEmailDomain\u003c/code\u003e, \u003ccode\u003eGroupCompanyLinks\u003c/code\u003e (только kind=company → компания по домену; person-kind не трогаем; company без правила — unmapped; два домена → одна компания схлопываются)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eRunCompanies\u003c/code\u003e (write/dry-run): FindCompany → если нет CreateCompany → GetContact + UpdatePerson с companyId; повтор = 0 изменений (found/already); роль-ящик не в CRM — notfound\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ebackfill.go\u003c/code\u003e: \u003ccode\u003eBackfillChanges\u003c/code\u003e (тег если нет; about-сводка только если about пуст/пробельный — ручные правки не перезаписываем), \u003ccode\u003eRunBackfill\u003c/code\u003e (hasTag по ListContactsByTag, about/имена GetContact; dry-run Would; CreateContactTag идемпотентно; повтор = 0 Updated)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003eimport-network.go\u003c/code\u003e: флаги \u003ccode\u003e--backfill\u003c/code\u003e, `--companies \u0026lt;yaml\u003c/td\u003e\n\u003ctd\u003e-\u0026gt;` (после Run; оба идемпотентны; отчёты updated/would/unchanged + found/created/linked/already/unmapped)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eКосметика: \u003ccode\u003emailconv.SplitPersonName\u003c/code\u003e снимает трейлинговую « (Org)»-декорацию («Astrid Emde (FOSSGIS e.V.)» → Astrid/Emde; пилот #270: given «Astrid Emde (FOSSGIS»/family «e.V.)»)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD офлайн: mailconv SplitPersonName (декорация/без/вложенная/вся-в-скобках → fallback), ParseCompanies (сорт/трим/ошибки), GroupCompanyLinks (role-ящики/сорт/unmapped/person-kind не трогаем), BackfillChanges (аддитивность: ручной about цел, пробельный about — сводка)\u003c/td\u003e\n\u003ctd\u003edone — зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eИнтеграция с живым OO (\u003ccode\u003e//go:build integration\u003c/code\u003e, без creds — skip): role-ящик → компания создана+слинкован → повтор already=1; matched: тег+about дописаны, ручной about B не перезаписан, повтор = 0 Updated\u003c/td\u003e\n\u003ctd\u003edone (office.produktor.io)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/brain/oo-network-import.md (N-1.4: --backfill/--companies, границы: person-kind не линкуем, Name маппинга = display name компании CRM) + PLAN.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego test ./internal/mailconv/ ./internal/ooimport/ ./internal/network/\u003c/code\u003e\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003ego vet ./...\u003c/code\u003e green; integration \u003ccode\u003ego test -tags=integration ./internal/ooimport/\u003c/code\u003e\ngreen (live OO); CLI \u003ccode\u003ego vet -tags=onlyoffice_import_network ./bin/onlyoffice/\u003c/code\u003e.\nДедуп: реальных дублей Person/компаний пилот не выявил — merge не\nприменялся (только report; правило #271 «строго по явному списку»).\nBranch \u003ccode\u003efeat/oo-crm-company#271\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)\u003c/h2\u003e\u003ca id=\"user-content-2026-09-03--p-95-сервисные-клиенты-поверх-read-контракта-gitea-241-epic-239\" class=\"anchor\" aria-label=\"Permalink: 2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)\" href=\"#2026-09-03--p-95-сервисные-клиенты-поверх-read-контракта-gitea-241-epic-239\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eGoal: единый клиентский слой чтения brain — typed-клиент поверх read-контракта\n(P-9.4 #240) вместо того, что каждый потребитель зовёт bin/brain/HTTP/MCP\nпо-своему; плюс «гейт facts» (клиент не выдаёт not-confirmed как подтверждённое).\nКлиент читает только через контракт (HTTP :8630), kb.lbug напрямую не открывает.\u003c/p\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eItem\u003c/th\u003e\n\u003cth\u003eStatus\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003epkg/brainclient\u003c/code\u003e (cgo-free): \u003ccode\u003eClient\u003c/code\u003e (Config base/token/timeout, транспорт) + typed \u003ccode\u003eSearch\u003c/code\u003e/\u003ccode\u003eGet\u003c/code\u003e/\u003ccode\u003eStats\u003c/code\u003e/\u003ccode\u003eAudit\u003c/code\u003e → \u003ccode\u003einternal/contract\u003c/code\u003e-ответы; ответы валидируются контрактными валидаторами (формат + contract_version; несовместимый сервис — ошибка)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eГейт facts: \u003ccode\u003eGate(root, confidence)\u003c/code\u003e (confirmed только facts+confirmed/legacy-пустое), \u003ccode\u003eClient.Facts\u003c/code\u003e (search --root facts → confirmed[] / not_confirmed[] с reason), \u003ccode\u003eGateAudit\u003c/code\u003e (гистограмма facts-корня; not_confirmed\u0026gt;0 = нарушение). 2v2-противоречия (hypothesis, D16) в confirmed не попадают\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eCLI \u003ccode\u003ebin/brain/client.go\u003c/code\u003e (shebang, brain_client, cgo-free): search/get/stats/audit, --json/--root facts|info/--as-of/-n/--no-web/--base/--token; реализация в \u003ccode\u003epkg/brainclient/cli\u003c/code\u003e (Run, покрыта тестами); audit при not_confirmed\u0026gt;0 → exit 1\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTDD: gate-таблицы (facts+confirmed/legacy vs hypothesis/partial/info), GateSearch/GateAudit на фикстурах; httptest-клиент (typed-декод, валидация, ошибки HTTP, auth-header, root=facts параметр); CLI-проводка (флаги, JSON-формы, exit-коды)\u003c/td\u003e\n\u003ctd\u003edone — \u003ccode\u003ego test ./pkg/brainclient/...\u003c/code\u003e зелёные\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eLive на :8630 (db 105220 leafs): stats/audit (facts=21 confirmed, not_confirmed=0, exit 0), search --root facts (onlyoffice postgres → 1 confirmed), get --body (source «docker ps x compose:...»), info-хиты в default-режиме помечены (not confirmed)\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eДокументация: docs/brain/read-contract.md §«Клиентский слой (P-9.5)» (SDK/CLI, гейт, примеры search-факт + audit-карточка, кросс-репо путь через P-9.6) + skills/brain/SKILL.md §«Read через контракт» + PLAN.md\u003c/td\u003e\n\u003ctd\u003edone\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cp dir=\"auto\"\u003eКросс-репо (gator/cv): вынос read-контракта и клиента в публичный go-* модуль —\nP-9.6 (ADR, эпик #239); до него внешние потребители читают по форматам\nread-contract.md через HTTP/MCP/CLI.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eVerification: \u003ccode\u003ego vet ./...\u003c/code\u003e + \u003ccode\u003ego test ./pkg/brainclient/...\u003c/code\u003e green\n(CLI-логика в pkg/brainclient/cli входит в обычный ./...; bin/brain — shebang-\nmains за эксклюзивными тегами, по D14 компилируются пофайлово) —\nbin/brain/client.go собран и прогнан \u003ccode\u003ego run -tags=brain_client\u003c/code\u003e;\n\u003ccode\u003e./bin/facts/audit.go self\u003c/code\u003e ok. Branch \u003ccode\u003efeat/brain-client#241\u003c/code\u003e off \u003ccode\u003emain\u003c/code\u003e.\u003c/p\u003e\n\u003c/article\u003e","richTextTruncated":false,"renderedFileInfo":null,"symbols":{"timed_out":false,"not_analyzed":false,"symbols":[{"name":"PLAN — 2dph (deductionphile)","fully_qualified_name":"PLAN — 2dph (deductionphile)","kind":"section_1","ident_start":2,"ident_end":32,"extent_start":0,"extent_end":97605,"ident_utf16":{"start":{"line_number":0,"utf16_col":2},"end":{"line_number":0,"utf16_col":30}},"extent_utf16":{"start":{"line_number":0,"utf16_col":0},"end":{"line_number":976,"utf16_col":0}}},{"name":"What","fully_qualified_name":"What","kind":"section_2","ident_start":1257,"ident_end":1261,"extent_start":1254,"extent_end":1865,"ident_utf16":{"start":{"line_number":22,"utf16_col":3},"end":{"line_number":22,"utf16_col":7}},"extent_utf16":{"start":{"line_number":22,"utf16_col":0},"end":{"line_number":36,"utf16_col":0}}},{"name":"Decisions (recorded)","fully_qualified_name":"Decisions (recorded)","kind":"section_2","ident_start":1868,"ident_end":1888,"extent_start":1865,"extent_end":15085,"ident_utf16":{"start":{"line_number":36,"utf16_col":3},"end":{"line_number":36,"utf16_col":23}},"extent_utf16":{"start":{"line_number":36,"utf16_col":0},"end":{"line_number":79,"utf16_col":0}}},{"name":"Architecture","fully_qualified_name":"Architecture","kind":"section_2","ident_start":15088,"ident_end":15100,"extent_start":15085,"extent_end":17233,"ident_utf16":{"start":{"line_number":79,"utf16_col":3},"end":{"line_number":79,"utf16_col":15}},"extent_utf16":{"start":{"line_number":79,"utf16_col":0},"end":{"line_number":119,"utf16_col":0}}},{"name":"Schema (first pass)","fully_qualified_name":"Schema (first pass)","kind":"section_2","ident_start":17236,"ident_end":17255,"extent_start":17233,"extent_end":18057,"ident_utf16":{"start":{"line_number":119,"utf16_col":3},"end":{"line_number":119,"utf16_col":22}},"extent_utf16":{"start":{"line_number":119,"utf16_col":0},"end":{"line_number":134,"utf16_col":0}}},{"name":"Config","fully_qualified_name":"Config","kind":"section_2","ident_start":18060,"ident_end":18066,"extent_start":18057,"extent_end":18594,"ident_utf16":{"start":{"line_number":134,"utf16_col":3},"end":{"line_number":134,"utf16_col":9}},"extent_utf16":{"start":{"line_number":134,"utf16_col":0},"end":{"line_number":146,"utf16_col":0}}},{"name":"Tooling conventions","fully_qualified_name":"Tooling conventions","kind":"section_2","ident_start":18597,"ident_end":18616,"extent_start":18594,"extent_end":18962,"ident_utf16":{"start":{"line_number":146,"utf16_col":3},"end":{"line_number":146,"utf16_col":22}},"extent_utf16":{"start":{"line_number":146,"utf16_col":0},"end":{"line_number":154,"utf16_col":0}}},{"name":"Open questions (v2)","fully_qualified_name":"Open questions (v2)","kind":"section_2","ident_start":18965,"ident_end":18984,"extent_start":18962,"extent_end":20407,"ident_utf16":{"start":{"line_number":154,"utf16_col":3},"end":{"line_number":154,"utf16_col":22}},"extent_utf16":{"start":{"line_number":154,"utf16_col":0},"end":{"line_number":175,"utf16_col":0}}},{"name":"Mail pipeline (done)","fully_qualified_name":"Mail pipeline (done)","kind":"section_2","ident_start":20410,"ident_end":20430,"extent_start":20407,"extent_end":21910,"ident_utf16":{"start":{"line_number":175,"utf16_col":3},"end":{"line_number":175,"utf16_col":23}},"extent_utf16":{"start":{"line_number":175,"utf16_col":0},"end":{"line_number":199,"utf16_col":0}}},{"name":"CI/CD pipeline (D15)","fully_qualified_name":"CI/CD pipeline (D15)","kind":"section_2","ident_start":21913,"ident_end":21933,"extent_start":21910,"extent_end":22529,"ident_utf16":{"start":{"line_number":199,"utf16_col":3},"end":{"line_number":199,"utf16_col":23}},"extent_utf16":{"start":{"line_number":199,"utf16_col":0},"end":{"line_number":212,"utf16_col":0}}},{"name":"Execution order","fully_qualified_name":"Execution order","kind":"section_2","ident_start":22532,"ident_end":22547,"extent_start":22529,"extent_end":23033,"ident_utf16":{"start":{"line_number":212,"utf16_col":3},"end":{"line_number":212,"utf16_col":18}},"extent_utf16":{"start":{"line_number":212,"utf16_col":0},"end":{"line_number":222,"utf16_col":0}}},{"name":"Gap to v1 (epic #16)","fully_qualified_name":"Gap to v1 (epic #16)","kind":"section_2","ident_start":23036,"ident_end":23056,"extent_start":23033,"extent_end":24473,"ident_utf16":{"start":{"line_number":222,"utf16_col":3},"end":{"line_number":222,"utf16_col":23}},"extent_utf16":{"start":{"line_number":222,"utf16_col":0},"end":{"line_number":239,"utf16_col":0}}},{"name":"2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)","fully_qualified_name":"2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)","kind":"section_2","ident_start":24476,"ident_end":24621,"extent_start":24473,"extent_end":27058,"ident_utf16":{"start":{"line_number":239,"utf16_col":3},"end":{"line_number":239,"utf16_col":128}},"extent_utf16":{"start":{"line_number":239,"utf16_col":0},"end":{"line_number":261,"utf16_col":0}}},{"name":"2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)","fully_qualified_name":"2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)","kind":"section_2","ident_start":27061,"ident_end":27154,"extent_start":27058,"extent_end":29646,"ident_utf16":{"start":{"line_number":261,"utf16_col":3},"end":{"line_number":261,"utf16_col":88}},"extent_utf16":{"start":{"line_number":261,"utf16_col":0},"end":{"line_number":284,"utf16_col":0}}},{"name":"2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)","fully_qualified_name":"2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)","kind":"section_2","ident_start":29649,"ident_end":29760,"extent_start":29646,"extent_end":31752,"ident_utf16":{"start":{"line_number":284,"utf16_col":3},"end":{"line_number":284,"utf16_col":83}},"extent_utf16":{"start":{"line_number":284,"utf16_col":0},"end":{"line_number":306,"utf16_col":0}}},{"name":"2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)","fully_qualified_name":"2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)","kind":"section_2","ident_start":31755,"ident_end":31852,"extent_start":31752,"extent_end":34266,"ident_utf16":{"start":{"line_number":306,"utf16_col":3},"end":{"line_number":306,"utf16_col":85}},"extent_utf16":{"start":{"line_number":306,"utf16_col":0},"end":{"line_number":329,"utf16_col":0}}},{"name":"2026-08-18 — Go-only write path sync (gitea #41)","fully_qualified_name":"2026-08-18 — Go-only write path sync (gitea #41)","kind":"section_2","ident_start":34269,"ident_end":34319,"extent_start":34266,"extent_end":35854,"ident_utf16":{"start":{"line_number":329,"utf16_col":3},"end":{"line_number":329,"utf16_col":51}},"extent_utf16":{"start":{"line_number":329,"utf16_col":0},"end":{"line_number":356,"utf16_col":0}}},{"name":"2026-08-22 — Typed config (gitea #90, epic #88)","fully_qualified_name":"2026-08-22 — Typed config (gitea #90, epic #88)","kind":"section_2","ident_start":35857,"ident_end":35906,"extent_start":35854,"extent_end":37203,"ident_utf16":{"start":{"line_number":356,"utf16_col":3},"end":{"line_number":356,"utf16_col":50}},"extent_utf16":{"start":{"line_number":356,"utf16_col":0},"end":{"line_number":373,"utf16_col":0}}},{"name":"2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)","fully_qualified_name":"2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)","kind":"section_2","ident_start":37206,"ident_end":37271,"extent_start":37203,"extent_end":38411,"ident_utf16":{"start":{"line_number":373,"utf16_col":3},"end":{"line_number":373,"utf16_col":66}},"extent_utf16":{"start":{"line_number":373,"utf16_col":0},"end":{"line_number":389,"utf16_col":0}}},{"name":"2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)","fully_qualified_name":"2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)","kind":"section_2","ident_start":38414,"ident_end":38487,"extent_start":38411,"extent_end":39454,"ident_utf16":{"start":{"line_number":389,"utf16_col":3},"end":{"line_number":389,"utf16_col":63}},"extent_utf16":{"start":{"line_number":389,"utf16_col":0},"end":{"line_number":402,"utf16_col":0}}},{"name":"2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)","fully_qualified_name":"2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)","kind":"section_2","ident_start":39457,"ident_end":39542,"extent_start":39454,"extent_end":41054,"ident_utf16":{"start":{"line_number":402,"utf16_col":3},"end":{"line_number":402,"utf16_col":84}},"extent_utf16":{"start":{"line_number":402,"utf16_col":0},"end":{"line_number":420,"utf16_col":0}}},{"name":"2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)","fully_qualified_name":"2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)","kind":"section_2","ident_start":41057,"ident_end":41130,"extent_start":41054,"extent_end":42178,"ident_utf16":{"start":{"line_number":420,"utf16_col":3},"end":{"line_number":420,"utf16_col":72}},"extent_utf16":{"start":{"line_number":420,"utf16_col":0},"end":{"line_number":439,"utf16_col":0}}},{"name":"2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)","fully_qualified_name":"2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)","kind":"section_2","ident_start":42181,"ident_end":42249,"extent_start":42178,"extent_end":43622,"ident_utf16":{"start":{"line_number":439,"utf16_col":3},"end":{"line_number":439,"utf16_col":69}},"extent_utf16":{"start":{"line_number":439,"utf16_col":0},"end":{"line_number":460,"utf16_col":0}}},{"name":"2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)","fully_qualified_name":"2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)","kind":"section_2","ident_start":43625,"ident_end":43696,"extent_start":43622,"extent_end":44900,"ident_utf16":{"start":{"line_number":460,"utf16_col":3},"end":{"line_number":460,"utf16_col":72}},"extent_utf16":{"start":{"line_number":460,"utf16_col":0},"end":{"line_number":478,"utf16_col":0}}},{"name":"2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)","fully_qualified_name":"2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)","kind":"section_2","ident_start":44903,"ident_end":44976,"extent_start":44900,"extent_end":47000,"ident_utf16":{"start":{"line_number":478,"utf16_col":3},"end":{"line_number":478,"utf16_col":74}},"extent_utf16":{"start":{"line_number":478,"utf16_col":0},"end":{"line_number":507,"utf16_col":0}}},{"name":"2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)","fully_qualified_name":"2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)","kind":"section_2","ident_start":47003,"ident_end":47111,"extent_start":47000,"extent_end":48533,"ident_utf16":{"start":{"line_number":507,"utf16_col":3},"end":{"line_number":507,"utf16_col":81}},"extent_utf16":{"start":{"line_number":507,"utf16_col":0},"end":{"line_number":525,"utf16_col":0}}},{"name":"2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)","fully_qualified_name":"2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)","kind":"section_2","ident_start":48536,"ident_end":48621,"extent_start":48533,"extent_end":50343,"ident_utf16":{"start":{"line_number":525,"utf16_col":3},"end":{"line_number":525,"utf16_col":86}},"extent_utf16":{"start":{"line_number":525,"utf16_col":0},"end":{"line_number":541,"utf16_col":0}}},{"name":"2026-08-22 — singleton-cache ingest model (gitea #110)","fully_qualified_name":"2026-08-22 — singleton-cache ingest model (gitea #110)","kind":"section_2","ident_start":50346,"ident_end":50402,"extent_start":50343,"extent_end":51659,"ident_utf16":{"start":{"line_number":541,"utf16_col":3},"end":{"line_number":541,"utf16_col":57}},"extent_utf16":{"start":{"line_number":541,"utf16_col":0},"end":{"line_number":558,"utf16_col":0}}},{"name":"2026-09-23 — shared embedding model on the search path (gitea #335)","fully_qualified_name":"2026-09-23 — shared embedding model on the search path (gitea #335)","kind":"section_2","ident_start":51662,"ident_end":51731,"extent_start":51659,"extent_end":53532,"ident_utf16":{"start":{"line_number":558,"utf16_col":3},"end":{"line_number":558,"utf16_col":70}},"extent_utf16":{"start":{"line_number":558,"utf16_col":0},"end":{"line_number":581,"utf16_col":0}}},{"name":"2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)","fully_qualified_name":"2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)","kind":"section_2","ident_start":53535,"ident_end":53616,"extent_start":53532,"extent_end":56063,"ident_utf16":{"start":{"line_number":581,"utf16_col":3},"end":{"line_number":581,"utf16_col":82}},"extent_utf16":{"start":{"line_number":581,"utf16_col":0},"end":{"line_number":609,"utf16_col":0}}},{"name":"2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)","fully_qualified_name":"2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)","kind":"section_2","ident_start":56066,"ident_end":56188,"extent_start":56063,"extent_end":58146,"ident_utf16":{"start":{"line_number":609,"utf16_col":3},"end":{"line_number":609,"utf16_col":81}},"extent_utf16":{"start":{"line_number":609,"utf16_col":0},"end":{"line_number":631,"utf16_col":0}}},{"name":"2026-08-23 — secret-scan в CI всех репо (gitea #142)","fully_qualified_name":"2026-08-23 — secret-scan в CI всех репо (gitea #142)","kind":"section_2","ident_start":58149,"ident_end":58212,"extent_start":58146,"extent_end":59352,"ident_utf16":{"start":{"line_number":631,"utf16_col":3},"end":{"line_number":631,"utf16_col":55}},"extent_utf16":{"start":{"line_number":631,"utf16_col":0},"end":{"line_number":643,"utf16_col":0}}},{"name":"2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)","fully_qualified_name":"2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)","kind":"section_2","ident_start":59355,"ident_end":59470,"extent_start":59352,"extent_end":62255,"ident_utf16":{"start":{"line_number":643,"utf16_col":3},"end":{"line_number":643,"utf16_col":89}},"extent_utf16":{"start":{"line_number":643,"utf16_col":0},"end":{"line_number":670,"utf16_col":0}}},{"name":"2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)","fully_qualified_name":"2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)","kind":"section_2","ident_start":62258,"ident_end":62348,"extent_start":62255,"extent_end":64346,"ident_utf16":{"start":{"line_number":670,"utf16_col":3},"end":{"line_number":670,"utf16_col":75}},"extent_utf16":{"start":{"line_number":670,"utf16_col":0},"end":{"line_number":690,"utf16_col":0}}},{"name":"2026-08-25 — CRM association two-source rule (gitea #55, epic #52)","fully_qualified_name":"2026-08-25 — CRM association two-source rule (gitea #55, epic #52)","kind":"section_2","ident_start":64349,"ident_end":64417,"extent_start":64346,"extent_end":66352,"ident_utf16":{"start":{"line_number":690,"utf16_col":3},"end":{"line_number":690,"utf16_col":69}},"extent_utf16":{"start":{"line_number":690,"utf16_col":0},"end":{"line_number":712,"utf16_col":0}}},{"name":"2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)","fully_qualified_name":"2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)","kind":"section_2","ident_start":66355,"ident_end":66447,"extent_start":66352,"extent_end":67665,"ident_utf16":{"start":{"line_number":712,"utf16_col":3},"end":{"line_number":712,"utf16_col":93}},"extent_utf16":{"start":{"line_number":712,"utf16_col":0},"end":{"line_number":729,"utf16_col":0}}},{"name":"2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)","fully_qualified_name":"2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)","kind":"section_2","ident_start":67668,"ident_end":67742,"extent_start":67665,"extent_end":69435,"ident_utf16":{"start":{"line_number":729,"utf16_col":3},"end":{"line_number":729,"utf16_col":75}},"extent_utf16":{"start":{"line_number":729,"utf16_col":0},"end":{"line_number":751,"utf16_col":0}}},{"name":"2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)","fully_qualified_name":"2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)","kind":"section_2","ident_start":69438,"ident_end":69526,"extent_start":69435,"extent_end":71688,"ident_utf16":{"start":{"line_number":751,"utf16_col":3},"end":{"line_number":751,"utf16_col":65}},"extent_utf16":{"start":{"line_number":751,"utf16_col":0},"end":{"line_number":773,"utf16_col":0}}},{"name":"2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)","fully_qualified_name":"2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)","kind":"section_2","ident_start":71691,"ident_end":71793,"extent_start":71688,"extent_end":73448,"ident_utf16":{"start":{"line_number":773,"utf16_col":3},"end":{"line_number":773,"utf16_col":74}},"extent_utf16":{"start":{"line_number":773,"utf16_col":0},"end":{"line_number":796,"utf16_col":0}}},{"name":"2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)","fully_qualified_name":"2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)","kind":"section_2","ident_start":73451,"ident_end":73528,"extent_start":73448,"extent_end":75145,"ident_utf16":{"start":{"line_number":796,"utf16_col":3},"end":{"line_number":796,"utf16_col":72}},"extent_utf16":{"start":{"line_number":796,"utf16_col":0},"end":{"line_number":817,"utf16_col":0}}},{"name":"2026-08-26 — весь mail-корпус в brain (gitea #199)","fully_qualified_name":"2026-08-26 — весь mail-корпус в brain (gitea #199)","kind":"section_2","ident_start":75148,"ident_end":75211,"extent_start":75145,"extent_end":76982,"ident_utf16":{"start":{"line_number":817,"utf16_col":3},"end":{"line_number":817,"utf16_col":53}},"extent_utf16":{"start":{"line_number":817,"utf16_col":0},"end":{"line_number":838,"utf16_col":0}}},{"name":"2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)","fully_qualified_name":"2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)","kind":"section_2","ident_start":76985,"ident_end":77079,"extent_start":76982,"extent_end":80467,"ident_utf16":{"start":{"line_number":838,"utf16_col":3},"end":{"line_number":838,"utf16_col":77}},"extent_utf16":{"start":{"line_number":838,"utf16_col":0},"end":{"line_number":858,"utf16_col":0}}},{"name":"2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)","fully_qualified_name":"2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)","kind":"section_2","ident_start":80470,"ident_end":80558,"extent_start":80467,"extent_end":84044,"ident_utf16":{"start":{"line_number":858,"utf16_col":3},"end":{"line_number":858,"utf16_col":77}},"extent_utf16":{"start":{"line_number":858,"utf16_col":0},"end":{"line_number":879,"utf16_col":0}}},{"name":"2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)","fully_qualified_name":"2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)","kind":"section_2","ident_start":84047,"ident_end":84193,"extent_start":84044,"extent_end":86752,"ident_utf16":{"start":{"line_number":879,"utf16_col":3},"end":{"line_number":879,"utf16_col":105}},"extent_utf16":{"start":{"line_number":879,"utf16_col":0},"end":{"line_number":901,"utf16_col":0}}},{"name":"2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)","fully_qualified_name":"2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)","kind":"section_2","ident_start":86755,"ident_end":86878,"extent_start":86752,"extent_end":90206,"ident_utf16":{"start":{"line_number":901,"utf16_col":3},"end":{"line_number":901,"utf16_col":95}},"extent_utf16":{"start":{"line_number":901,"utf16_col":0},"end":{"line_number":923,"utf16_col":0}}},{"name":"2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)","fully_qualified_name":"2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)","kind":"section_2","ident_start":90209,"ident_end":90360,"extent_start":90206,"extent_end":94249,"ident_utf16":{"start":{"line_number":923,"utf16_col":3},"end":{"line_number":923,"utf16_col":110}},"extent_utf16":{"start":{"line_number":923,"utf16_col":0},"end":{"line_number":951,"utf16_col":0}}},{"name":"2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)","fully_qualified_name":"2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)","kind":"section_2","ident_start":94252,"ident_end":94368,"extent_start":94249,"extent_end":97605,"ident_utf16":{"start":{"line_number":951,"utf16_col":3},"end":{"line_number":951,"utf16_col":86}},"extent_utf16":{"start":{"line_number":951,"utf16_col":0},"end":{"line_number":976,"utf16_col":0}}}]}},"codeViewLayoutRoute":{"repo":{"id":1330190220,"defaultBranch":"main","name":"2dph","ownerLogin":"eSlider","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2026-08-10T19:42:33.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/1188335?v=4","public":true,"private":false,"isOrgOwned":false,"isArchived":false},"currentUser":null,"uploadToken":"XHi-Hlh2jE7TgRc8ZHnJ1SC3OfHezysvTd51SRZiwxx2RaDlg2gxOtiyAcNkjADpoGah-bMInokqZTCJJretSw","allShortcutsEnabled":false,"treeExpanded":true,"path":"PLAN.md","symbolsExpanded":false,"refInfo":{"name":"main","listCacheKey":"v0:1790197666.0","canEdit":false,"currentOid":"19912d995208769bdc7a35dbcb356daff215660f"},"helpUrl":"https://docs.github.com","githubDevUrl":null},"codeViewFileTreeLayoutRoute":{"fileTree":{"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":"bin","path":"bin","contentType":"directory"},{"name":"deploy","path":"deploy","contentType":"directory"},{"name":"docs","path":"docs","contentType":"directory"},{"name":"etc","path":"etc","contentType":"directory"},{"name":"internal","path":"internal","contentType":"directory"},{"name":"pkg","path":"pkg","contentType":"directory"},{"name":"scripts","path":"scripts","contentType":"directory"},{"name":"skills","path":"skills","contentType":"directory"},{"name":"test","path":"test","contentType":"directory"},{"name":".dockerignore","path":".dockerignore","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":".gitleaks.toml","path":".gitleaks.toml","contentType":"file"},{"name":"AGENTS.md","path":"AGENTS.md","contentType":"file"},{"name":"Dockerfile","path":"Dockerfile","contentType":"file"},{"name":"Dockerfile.liteparse","path":"Dockerfile.liteparse","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"PERF.md","path":"PERF.md","contentType":"file"},{"name":"PLAN.md","path":"PLAN.md","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"compose.yaml","path":"compose.yaml","contentType":"file"},{"name":"go.mod","path":"go.mod","contentType":"file"},{"name":"go.sum","path":"go.sum","contentType":"file"},{"name":"go.work","path":"go.work","contentType":"file"},{"name":"go.work.sum","path":"go.work.sum","contentType":"file"}],"totalCount":25}},"fileTreeProcessingTime":21.848451,"foldersToFetch":[]},"codeViewBlobLayoutRoute":{"codeLineWrapEnabled":false,"refInfo":{"name":"main","listCacheKey":"v0:1790197666.0","canEdit":false,"refType":"branch","currentOid":"19912d995208769bdc7a35dbcb356daff215660f","canEditOnDefaultBranch":false,"fileExistsOnDefault":true},"path":"PLAN.md","blob":{"copilotSWEAgentEnabled":false,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/eSlider/2dph/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"PLAN.md","displayUrl":"https://github.com/eSlider/2dph/blob/main/PLAN.md?raw=true","headerInfo":{"blobSize":"95.3 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"c1722b8","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FeSlider%2F2dph%2Fblob%2Fmain%2FPLAN.md","isCSV":false,"isRichtext":true,"lineInfo":{"truncatedLoc":"976","truncatedSloc":"806"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"isIssueTemplate":false,"isDiscussionTemplate":false,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/eSlider/2dph/blob/main/PLAN.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/eSlider/2dph/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/eSlider/2dph/raw/refs/heads/main/PLAN.md","renderImageOrRaw":false,"shortPath":null,"symbolsEnabled":true,"tabSize":4,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null},"copilotInfo":null,"copilotAccessAllowed":false,"copilotSpacesEnabled":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"isMarketplaceEnabled":true},"codeViewBlobLayoutRoute.StyledBlob":{"rawLines":["# PLAN — 2dph (deductionphile)","","A brain that loves facts and deduction. Evidence-first knowledge graph + hybrid","RAG over the operational Brain/ops/eSlider stack. Built like Sherlock","Holmes: nothing is asserted unless it has proof.","","Status: **v1 in** (epic [#16](https://git.produktor.io/eSlider/2dph/issues/16) closed).","v2 board: milestone [v2](https://git.produktor.io/eSlider/2dph/milestone/13) —","OCR [#6](https://git.produktor.io/eSlider/2dph/issues/6) in,","[#29](https://git.produktor.io/eSlider/2dph/issues/29) OQ1 in,","[#30](https://git.produktor.io/eSlider/2dph/issues/30) OQ3 in,","[#34](https://git.produktor.io/eSlider/2dph/issues/34) D23 in,","[#36](https://git.produktor.io/eSlider/2dph/issues/36) OQ5/D24 in,","[#102](https://git.produktor.io/eSlider/2dph/issues/102) gs PDF normalize in,","[#163](https://git.produktor.io/eSlider/2dph/issues/163) browser-sync in","(`bin/cron/browser-sync.go` + 6-hourly systemd timer, reads","`var/corpus/{gmail,linkedin,djinni}` → POST /ingest; Thorium down tolerated),","[#82](https://git.produktor.io/eSlider/2dph/issues/82) Synapse Matrix (leafs+edges","as a service for pc-agent) in — `bin/brain/synapse-matrix.go`, docs","[docs/brain-synapse.md](docs/brain-synapse.md).","Gap: [docs/roadmap.md](docs/roadmap.md).","","## What","","A single embedded store (LadybugDB, one `.lbug` file) that holds:","","- a **property graph** (Cypher) over the ops corpus and portfolio,","- **FTS5** (BM25) and **HNSW vector** indexes (model2vec embeddings),","- every node/edge annotated with `root` (`facts` | `info`), `confidence`","  (`confirmed` | `partial` | `hypothesis`), `evidence[]`, `how`, `where`, `when`.","","Search = **deduction**: facts root first, info root second, `web-search` as the","second independent source when local roots cannot confirm. Following the","detective method: **a fact needs ≥2 independent sources or it is","`(not confirmed)`.**","","## Decisions (recorded)","","| # | Question | Answer |","|---|----------|--------|","| D1 | RAG corpus | ops stack (chat, onlyoffice, gitea/NPM, searchxng, observability, ai-bot, mcp-servers, `~/.ssh/config`) + portfolio. Exclude `office.dev` + jobs/applications. |","| D2 | skill merging | integrate skills **in this project** `skills/`; skip gitea / brain-dependent skills. |","| D3 | web search | Go client `bin/web/search.go` (`internal/websearch`). SearXNG URL is config (`BRAIN_SEARCH_URL`). Optional Compose profile `searxng` (sanitized settings). Do not run a second copy on a host that already has one. Empty/`throttled` ≠ “nothing exists”. |","| D4 | embeddings | **model2vec** `minishlab/potion-multilingual-128M` instead of embeddinggemma. |","| D5 | parser | **mistune** for MD → leaf extraction (duckdb-md documented as future optional SQL/export layer, not v1). |","| D6 | graph engine | **LadybugDB**. Go is the service (`bin/brain/search.go`, `bin/brain/serve.go` in-process, `internal/brain`). Read path is Go + Zig CGO (D21). Python `bin/kb/{get,stats,eval}` is the CI fallback when Zig/libs are not fetched. Incremental write is Python `bin/kb/add` (`bin/brain/add.go`). Bulk rebuild stays `compose --profile index` until the Go write path is safe. |","| D7 | db access | `db-yaml`/`psql-yq`-style, read-only, YAML out. OnlyOffice Postgres via SSH tunnel (`127.0.0.1:5433`). |","| D8 | evidence | detective method: ≥2 independent sources or `(not confirmed)`. 2-source auto-pair docker ps × compose × ssh-config × docs. |","| D9 | facts/goal model | Who / What / How / Where / When + evidence + confidence on every edge. |","| D10 | versioning | everything is a leaf with `sha256 + observed_at + source_rev`; `File-[:HAS_VERSION]-\u003eCommit-[:AUTHORED]-\u003ePerson`. Stale = `source_rev` \u003c git HEAD. |","| D11 | strong/weak | `root` column: `facts` (strong) vs `info` (weak). Answer is `confirmed` only from facts root. |","| D12 | transactional | facts and info split by root but **written in the same Ladybug transaction (ACID)** on every write. |","| D13 | portfolio | start graph `(Person:eslider)-[:HAS]-\u003e(Portfolio)`, associate other natural/juristic persons later. |","| D14 | tooling style | `bin/{subject}/{method}.go` shebang (e.g. `bin/brain/search.go`). Shared code in `internal/`. One root `go.mod` + `go.work`. No `bin/*/main.go`, no nested modules. |","| D15 | repo | Gitea [`eSlider/2dph`](https://git.produktor.io/eSlider/2dph) is origin + [issues](https://git.produktor.io/eSlider/2dph/issues). GitHub `eSlider/2dph` is the public clone (PRs + Actions CI). No direct `main` pushes. TDD → PR → CI green → merge. |","| D16 | contradictions | ≥2 yes vs ≥2 no → hypothesis → `(not confirmed)` until a rule fires. Order: **temporal_freshness** (fresh ≥2 vs stale minority), then **authority_pairing** (runtime/config A×B beats narrative C). Store as `a x b vs c x d` on hypothesis leafs. `bin/facts/audit contradict`. [#29](https://git.produktor.io/eSlider/2dph/issues/29). |","| D17 | assertion gate | Fact-check every *claim* (facts → info → live → web), not every edit. `bin/brain/search.go` adds a `web` block when there is no facts hit (`throttled`/`skipped`/`refused` ≠ absence). `--root` and `--no-web` stay local. Missing graph ≠ “does not exist”. |","| D18 | reasoner | Pluggable OpenAI-compatible URL (`REASONER_BASE_URL`). RAM: `Qwen/Qwen3.5-9B`. Quality: `prism-ml/Bonsai-27B-gguf` or `Qwen/Qwen3.6-27B`. No official Qwen3.6-9B. CPU bake-off: `bin/reasoner/bench.go` + compose profile `reasoner` (`OLLAMA_NUM_GPU=0`, `:11435`). PicoClaw is compose profile `picoclaw`; tools are `search`/`get`/`audit`. Weights are not copied into the 2dph image. Agent lever/loop: [#15](https://git.produktor.io/eSlider/2dph/issues/15). |","| D19 | git history | [go-git](https://github.com/go-git/go-git) via `bin/brain/import-git.go` (lib `internal/gitlog`). No subprocess of the git binary. Reads history and upserts commit leafs into the brain directly (`--dry-run` to preview). |","| D20 | agent API | OpenAPI + MCP are generated from the same `pkg/httpapi.Ops` table as `bin/brain/serve.go` handlers. `GET /openapi.json`, `POST /mcp` (JSON-RPC tools/list + tools/call). Tool names match OpenAPI paths (`search`/`get`/`stats`/`audit`/`ingest`). |","| D21 | CGO | Ladybug/tokenizers CGO is compiled with **Zig** (`bin/cgo/zcc` → `zig cc -target …-linux-gnu`), not gcc. `bin/cgo/zig` pins Zig 0.14.1 + liblbug 0.19.1 + libtokenizers 1.27.0. Compose `target: api` has no CPython; write/rebuild is profile `index`. |","| D22 | analytics | **duckdb-go** in-process (`pkg/duckdb`, `bin/jsonl/stats.go`) for quantiles/JSONL. Links with **gcc/g++**, not Zig. Ladybug stays the graph; web-search cache stays modernc sqlite. Slice small structured docs with **mikefarah/yq**, not kislyuk/jq. [#30](https://git.produktor.io/eSlider/2dph/issues/30). |","| D23 | CLI | **flaggy** (`github.com/integrii/flaggy`, 0 deps). Flags at any position. Wrapper `pkg/cli`. Bash complete: `source \u003c(./bin/shell/complete.go bash)`. No cobra, no stdlib `flag` in Go tools. Search does not intercept the word `completion`. [#34](https://git.produktor.io/eSlider/2dph/issues/34). |","| D24 | fact intervals | Leaf `valid_from` / `valid_to` (YYYY-MM-DD, inclusive; empty = open/legacy). Search `--as-of` / MCP `as_of` keeps facts active that day. Not D16 `temporal_freshness` (source stale vs HEAD). Empty interval = always visible. [#36](https://git.produktor.io/eSlider/2dph/issues/36). |","| D25 | deploy data path | Brain serves from host `var/`, not named volumes. Compose binds `./var:/data/var` (kb.lbug at `/data/var/kb.lbug`), HF model from `var/hf`, Ladybug FTS/VECTOR extensions mounted read-only into `$HOME/.lbdb/extension`. `brain` uses `network_mode: host` so `127.0.0.1:8630` works with any image (KB_HOST-independent). Named volumes `kb-model`/`kb-var` dropped — live data is host `var/` (gitignored). |","| D26 | search A/B (#201/#202) | Ничего не «лучше» без A/B. `bin/brain/bench.go` + golden-set (`internal/brain/testdata/golden-set.json`, ~50 запросов, рус+англ, факты/mail/docs/git/ssh). Метрики: latency p50/p95/mean, recall@5/@10 (fragment-гейт как eval.go + A/B vs baseline IDs), CPU/RSS из /proc. Гейты: recall@5 ≥ 0.95, кандидат p50 ≤ baseline × 1.5. Baseline: линейный скан 313k = p50 ~31s. Кандидаты (ANN/SIMD/FTS-first) — child issues эпика #201. |","| D32 | ANN вне liblbug (#201/#204) | Инкрементальный векторный индекс в `internal/brain/ann`: **IVF** (k-means + точный косинус по probed-ячейкам, чистый Go; `coder/hnsw`/HNSW отброшен — на 313k реальных достижимы 34% узлов, recall@5 0.16). Индекс `var/state/vector.ann` + append-only WAL (`upsert` = только новые leafs, без rebuild). `bin/brain/ann.go` (build/upsert/ensure/stats/api/search), wave-шаг `ann-build` (#206). `queryVector`: индекс top-k → point-lookup метаданных → слияние с FTS (rank не тронут); fallback на скан при отсутствии/повреждении. Конфиг `vector.ann` (NList=2000, NProbe=128 — дефолты пакета; прод-конфиг nprobe=2000, решение #206). A/B: baseline p50 ~31s → кандидат p50 ~0.4s, recall@5 vs baseline 1.0 (цифры в #204/#206). |","| D26 | repo layout | `bin/{subject}` = tools only (`{verb}-{object}.go`); executables/orchestration → `scripts/`; data tree `var/{tmp,dist,log,state,cache,hf,kb.lbug,corpus/{git,mail,chats,docs}}`; configs `etc/{subject}` + central `etc/brain/config.yml`. CI guard fails on absolute paths and Python remnants. [#89](https://git.produktor.io/eSlider/2dph/issues/89) |","| D27 | config system | All external sources (paths, URLs, endpoints) via `github.com/eslider/go-config` (`internal/config`): stack `etc/brain/config.yml` → `config.local.yml` → `.env` → process env; one load per process into a strict typed struct; deep merge (maps recurse, scalars last-write-wins, keys lower+alnum); no ad-hoc `os.Getenv`; legacy `KB_*` / `BRAIN_SEARCH_*` env names honored transitionally. [#90](https://git.produktor.io/eSlider/2dph/issues/90), [#91](https://git.produktor.io/eSlider/2dph/issues/91) |","| D28 | no hardcoded paths/URLs | Nothing like `/mnt/…`, `/home/\u003cuser\u003e/…`, or pinned host URLs in code/comments/tests; missing config key = explicit error pointing at `etc/brain/config.yml`. CI-enforced. [#89](https://git.produktor.io/eSlider/2dph/issues/89) |","| D29 | typed code | Static typing first: no `map[string]any` at boundaries; loose→strict via go-viper/mapstructure/v2; primitives only in `pkg/utils/*.go`. Hotspots listed in [#92](https://git.produktor.io/eSlider/2dph/issues/92). |","| D30 | owned repos | All `github.com/eSlider/go-*` are owned: Gitea `git.produktor.io/eSlider/*` = working home (issues/releases only there), GitHub = push-mirror; module paths stay `github.com/eSlider/go-*`. Shared template: subpackage-per-domain. Gitea fetch via `replace` in `go.mod`: `go-config@v0.4.0`, `go-onlyoffice@v0.14.0` → `git.produktor.io/eSlider/*` (go.sum carries Gitea hashes only; zero `github.com/eSlider/go-*` downloads). [#101](https://git.produktor.io/eSlider/2dph/issues/101) |","| D31 | concurrency | Public APIs synchronous; ctx-first IO; bounded pools; wg-accounted goroutines; sender closes channels; errgroup for fan-out/fan-in; backpressure over unbounded queues; graceful shutdown; `go test -race` in CI. [#94](https://git.produktor.io/eSlider/2dph/issues/94) |","| D32 | zero-alloc hot paths | Pre-allocation, buffer reuse (`buf[:0]`), `sync.Pool` with reset, `strings.Builder` over `+`, sets as `map[K]struct{}`, structs over maps; proven by `-benchmem`. Applies to hot paths only — no premature optimization. |","| D33 | sync-ETL reimplementation | Pipeline `Source.Fetch(ctx,cursor)→[]Blob→Registry.Decode→Transform→Load`; one Handler per format (eml=emersion/go-message `Walk()`, zip=`archive/zip`, html=`x/net/html` optional, pdf=Ghostscript normalize→pdftotext/tesseract); lazy children, walker safety limits; single implementation per transformer; canonical `Conversation` model on disk (`var/corpus`) → brain graph (`SENT/TO/CC/BCC/REPLY_TO/PART_OF`); URL addressing `scheme://platform/path[#anchor]` with `sha256(URL)[:16]` node IDs. Epic [#88](https://git.produktor.io/eSlider/2dph/issues/88): [#95](https://git.produktor.io/eSlider/2dph/issues/95)–[#102](https://git.produktor.io/eSlider/2dph/issues/102). |","| D34 | synapse edges | User-defined edges between neurons modeled as a `SYNAPTIC (FROM Leaf TO Leaf, type STRING)` rel table (leafs = neurons, edges = synapses); adjacency = both directions; path = `[:SYNAPTIC* ALL SHORTEST 1..N]` (Ladybug exposes only intermediate nodes → rebuild `[from, mid…, to]`). Service `bin/brain/synapse-matrix.go`, auth = token OR loopback bind, config section `synapse`. [#82](https://git.produktor.io/eSlider/2dph/issues/82) |","| D35 | ANN в проде (#206) | Победитель A/B (#204) включён по умолчанию: `vector.ann.enabled: true` (serve + CLI), **nprobe=2000** — замерено на живом корпусе 313689 векторов (11 запросов golden-set): p50 471ms recall@5=1.0 vs 256 (p50 262ms recall@5 0.818) — корректность сначала, тюнинг позже. Волна: шаг `ann-build` = `ann.go ensure` — полный build только когда индекса нет/устарел (\u003e10% отставания по счётчику leafs), иначе инкрементальный WAL-upsert (+100 \u003c1s, никогда rebuild на волну). `serve` — тёплый старт: индекс грузится при старте (WarmANN), при пустом/битом — fallback на скан, build делает волна (single-writer, #204). A/B прод: p50 471ms vs baseline 33.6s (0.014x), recall@5 vs baseline 1.0. |","| D36 | FTS-фаза rebuild (#244) | После write-фазы `brain-index` закрывает хэндл и переоткрывает БД с автоподобранным пулом `max(1GB, chars×32)` для `CREATE_FTS_INDEX`: на полном корпусе (280MB текста) 1GB-пул падает и оставляет орфан `0_id_appears_info`, недостижимый через SQL (внутренний каталог). Замеры порога: 10.3MB→64/128MB, 10.8MB→192/256MB, прод 280MB→1GB/10GB. Двухфазный workaround из rebuild.md автоматизирован (отменён как ручной). C-потолок (замер #244): write-фаза ~140KB/leaf линейно, пул 1GB не ограничивает, CHECKPOINT/embedding не влияют, close не освобождает в том же процессе; FTS — пул ~9GB на полном корпусе. Критерий #237 (RSS ≤2-4GB) переформулирован: Go-часть — да; C-сторона — задокументированный потолок (~14-15GB write + ~9GB FTS на 105k). |","","## Architecture","","```","2dph/","  PLAN.md / AGENTS.md","  docs/                     published docs (this conversation → docs/ as md)","  skills/                   in-project skills (web-search, postgres, brain, picoclaw, diataxis-docs)","  bin/","    brain/search.go         deduction: facts → info → web","    brain/index.go          rebuild FTS + HNSW (incl. --with-mail)","    brain/add.go            incremental leaf write (no rebuild)","    brain/serve.go          HTTP API in-process + OpenAPI/MCP (D20); Zig CGO (D21)","    brain/get.go stats.go eval.go watch.go model.go   # Go read/ops (cgo)","    facts/extract.go audit.go crm.go  # 2-source pairing, confidence, CRM proof","    mail/sync.go import.go convert-mbox.go ocr.go    # mail ETL (Gmail/OO/M365), PDF OCR","    mail/incubator.go       # legacy .eml → doveadm save в mail-server-инкубатор (#252/эпик #250)","    chats/sync.go import.go facts.go apply.go  # conversations (no chats index)","    contacts/*.go           CRM contacts importer","    web/search.go           SearXNG client (throttled ≠ absence)","    git/import.go           go-git history (no git binary; conversion only)","    markdown/import.go      H2 leaf split (Go)","    postgres/query.go       read-only YAML (wraps scripts/db/psql-yq)","    reasoner/bench.go        CPU tool-call bench (D18; OpenAI tools)","    qa/stats.go             DuckDB quantiles / JSONL (D22; not the test taxonomy)","    semver/next.go          next semver from conventional commits (Release job)","    cli/complete.go         flaggy bash/zsh/fish complete (D23)","    cgo/zig zcc zc++        CGO toolchain (zig cc, not gcc)","    stack/start start-assistant start-mail-sync stop status","    db/psql-yq              (vendored)","    ssh-tunnel              onlyoffice pg tunnel 5433","  test/","    system/                 offline-gated system tests (CI default)","    stress/                 live-brain load generator","    integration/            opt-in live-dependency tests","    README.md               how to run each tier","  etc/","    searxng/ picoclaw/      operator config (FHS; moved from deploy/)","  var/kb.lbug               single embedded store (gitignored)","```","","## Schema (first pass)","","Node tables: `Person, Service, Host, Container, Repo, File, Commit, Leaf`.","`Leaf(embedding FLOAT[N])` — FTS on `text`. Векторный поиск: ANN IVF вне","liblbug (`internal/brain/ann`, #204; штатный liblbug HNSW Leaf_vec крашится","на росте графа, #192 → встроенный скан как fallback).","Edges: `RUNS / USES / FROM_FILE / HAS_VERSION / AUTHORED / ABOUT / ASSOCIATED / SIMILAR_0.85`.","`FROM_FILE` / `HAS_VERSION` / `AUTHORED`: `bin/brain/search.go --hop N` walks","them from each hit (1=File, 2=Commit, 3=Person). Rebuild writes","`Leaf-[:FROM_FILE]-\u003eFile`; git import writes the rest.","","Common props on every node/edge: `root`, `confidence`, `evidence[]`, `how`,","`where`, `when`, `source_rev`. Leaf interval of truth (D24): `valid_from`,","`valid_to`.","","## Config","","`~/.config/brain/` (0600):","","- `search.env` — real `BRAIN_SEARCH_URL/USER/PASS` from `~/.config/ops/npm-bot.env`","- `db-profiles.yml` — real `onlyoffice` profile (SSH tunnel `127.0.0.1:5433`,","  user `onlyoffice`, db `onlyoffice`, `network: host`,","  `password_env_file: ~/.config/ops/onlyoffice.env`) + example profiles.","  `~/.config/ops/onlyoffice.env` is bootstrapped from the VM's","  `/etc/onlyoffice/documentserver/local.json` (dbUser/dbPass), never committed.","- `~/.config/brain/../` — nothing else lives in the repo.","","## Tooling conventions","","- `bin/{subject}/{method}` — line 2 is a usage comment (mirrors `psql-yq`).","- bash + golang primary (Go shebang); no Python in-tree.","- YAML default output, `--json` for machines. Slice with mikefarah/yq.","- Everything that touches the network / DB is read-only, throttled, cached.","- Tests (TDD) gate every commit; `gh` + CI/CD on every push.","","## Open questions (v2)","","- OQ1: **in** — D16 adjudication: `temporal_freshness` then `authority_pairing`.","  Unresolved 2v2 stays hypothesis. [#29](https://git.produktor.io/eSlider/2dph/issues/29).","- OQ2: OCR — **in**. `pdftotext -layout` first; scans `pdftoppm` + tesseract","  `eng+deu` (`bin/mail/ocr.go`, `internal/ocr`). No gocv, no gosseract CGO","  (D21 Zig owns Ladybug CGO). Optional `OCR_ENGINE=paddle` / compose profile","  `ocr-paddle`. Docling left the default path. When `pdftotext` yields no text","  layer (scanned / export-locked / oversized), **Ghostscript normalizes the PDF","  first** (`internal/ocr.NormalizePDF`, gs `pdfwrite` → `var/tmp`, original","  preserved, artifact ≤ original): strips export-protection + shrinks so the","  pdftotext fast path / tesseract fallback see a clean PDF. Clean PDFs skip gs","  (happy path stays fast). [#6](https://git.produktor.io/eSlider/2dph/issues/6),","  [#102](https://git.produktor.io/eSlider/2dph/issues/102).","- OQ3: **in** — duckdb-go (`pkg/duckdb`, `bin/jsonl/stats.go`) for","  quantiles / JSONL count. Not a second graph. [#30](https://git.produktor.io/eSlider/2dph/issues/30).","- OQ4: YAML-first storage for leafs — deferred: JSON is ~10x faster to","  serialize and unambiguous; YAML only where humans edit files.","- OQ5: **in** — fact `valid_from` / `valid_to` + `--as-of` / MCP `as_of` (D24).","  Not D16 `temporal_freshness`. [#36](https://git.produktor.io/eSlider/2dph/issues/36).","","## Mail pipeline (done)","","1. `bin/mail/sync.go` (Go, 8 workers) — paginated Gmail / OnlyOffice / M365","   Graph download. Gmail attachments key off `body.attachmentId`, not MIME","   `partId`. M365 uses client-credentials + delta link (commit after success).","   M365 inline attachments are kept (#312): stored in `attachments/` under","   `inline-\u003chash8\u003e-\u003cname\u003e`, flagged `isInline`/`contentId` in `message.json`.","2. `bin/mail/import.go --from-raw` — message.json → message.md; PDFs via","   `pdftotext -layout` (~15ms); textless/scanned PDFs `pdftoppm` + tesseract","   `eng+deu`. PDFs without a readable text layer are first normalized with","   Ghostscript (`internal/ocr.NormalizePDF`, gs `pdfwrite`; original preserved,","   gs artifact in `var/tmp`, removed after extraction). ICS sidecars","   Latin-1→UTF-8 normalized.","3. `bin/brain/index.go --rebuild` — fresh rebuild (repo corpus + mail) because ladybug","   corrupts its WAL on bulk-insert into an already-indexed DB. Conversion and","   indexing stay separate for crash safety."," 4. Compose `mail-sync` / `scripts/stack/start-mail-sync` — ETL loop (default","    `onlyoffice,gmail`, 300s): download (existing mailsync binary) → bounded","    runner pipeline import (#98) with per-run stats YAML; full rebuild only if","    `MAIL_SYNC_INDEX=1`. Bot digests (ai-bot) and case wrappers reuse sync/OAuth;","    they do not replace the corpus path.","5. Result: 17,835 messages → 28,918 info leafs, FTS + HNSW healthy, searchable","   via `bin/brain/search.go`.","","## CI/CD pipeline (D15)","","`.github/workflows/ci.yml`:","","1. go vet + go test ./... (root module; packages without ladybug cgo)","2. `go test ./internal/brain/rank` (cgo-free ranking + flag parser)","3. `bin/facts/audit self` (lexicon internal consistency; `bin/facts/audit.go` is the D14 wrapper)","4. `bin/brain/eval.go` via Zig (recall@5 ≥ 0.95) — the CI SoT.","5. `bin/cgo/zig go build -tags system_ladybug` (compile search with zig cc; fetches pinned zig+libs).","","Feedback loop: every commit → PR → CI → green/gate → merge. Same discipline as","`db/tech-poc`: contract first where there is an OpenAPI/message shape.","","## Execution order","","1. scaffold repo (:done after this file + AGENTS.md + .gitignore + ci)","2. gh repo create eSlider/2dph --private + initial commit + CI","3. vendored skill integration (web-search, postgres, brain, diataxis-docs) — no remote links","4. schema + tools with TDD (kb + md + facts + brain)","6. ~/.config/brain config","7. corpus extraction (facts/info) — **in**: [#18](https://git.produktor.io/eSlider/2dph/issues/18)","8. verify: web-search smoke, onlyoffice pg, md-db round-trip, eval, audit","","## Gap to v1 (epic #16)","","Remaining: none for epic #16 (v1). Board:","[epic #16](https://git.produktor.io/eSlider/2dph/issues/16),","milestone [v1 detective brain](https://git.produktor.io/eSlider/2dph/milestone/12).","Narrative: [docs/roadmap.md](docs/roadmap.md).","","| Order | Issue | Gap |","|-------|-------|-----|","| 1 | [#14](https://git.produktor.io/eSlider/2dph/issues/14) | **in** — `bin/brain/add.go` / `POST /ingest` write facts+info without deleting `kb.lbug`. Bulk corpus still `--rebuild`. Leftover Python (mail/facts) is not the living-graph blocker. |","| 2 | [#17](https://git.produktor.io/eSlider/2dph/issues/17) | **in** — `--hop N` walks `FROM_FILE` → `HAS_VERSION` → `AUTHORED` (max 3). |","| 3 | [#18](https://git.produktor.io/eSlider/2dph/issues/18) | **in** — `--with-facts` / `--facts-json` land `root=facts`; `--with-chats` indexes `var/corpus/chats/md`. WhatsApp sync is in v1 via [#87](https://git.produktor.io/eSlider/2dph/pulls/87). |","| 4 | [#15](https://git.produktor.io/eSlider/2dph/issues/15) | **in** — lever/loop documented (`search` → `get` → `audit`). |","| 5 | [#19](https://git.produktor.io/eSlider/2dph/issues/19) | **in** — CI recall SoT is `bin/brain/eval.go` via Zig. |","","Does **not** block epic close: OQ4. OCR [#6](https://git.produktor.io/eSlider/2dph/issues/6), OQ1 [#29](https://git.produktor.io/eSlider/2dph/issues/29), OQ3 [#30](https://git.produktor.io/eSlider/2dph/issues/30) are **in**.","","## 2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)","","Goal: перевести эмпирические URL-рецепты (#56) в машинные проверки законов","Vinogradov на уровне URL: identity (канонический URL — одно написание),","contradiction (¬(P∧¬P) на одном URL), excluded middle (нечёткие claim → не","FACT, а open_questions), sufficient reason (verdict accept только на","FACT-/OPEN- premises). Ядро — cgo-free `internal/facts` (по образцу","Adjudicate/CheckFactRow), CLI — подкоманда `bin/facts/audit.go formal`","(JSON facts/cards на stdin → JSON-флаги, exit 0/1).","","| Item | Status |","|------|--------|","| `facts.CanonicalURL` — локальная канонизация URL по конвенции gator G-8.1 (lowercase scheme/host, default-port, utm_*/ref/gh_jid/token, сортировка query, без фрагмента/trailing slash); gator-модуль не импортируется | done |","| `internal/facts/formal.go`: `URLFact` (id/url/claim/attr/neg/conf/from/to) + `CheckURLIdentity` (merge/flag weaken), `CheckURLContradiction` (reject; только confirmed, пересечение D24), `CheckExcludedMiddle` (weaken → open_questions), `CheckSufficientReason` (accept без FACT-/OPEN- premises → reject), `CheckFormal` (агрегат, детерминизм) | done |","| CLI `bin/facts/audit.go formal` (shebang, hand-formatted): JSON {facts,cards} на stdin → {mode,ok,problems}; пример acceptance «вакансия активна» vs «вакансия closed» → contradiction verdict=reject | done |","| TDD: `canonical_test.go` (кросс-канал/utm/детерминизм/нормализация/ошибки) + `formal_test.go` (identity merge, contradiction acceptance + не-противоречия: same polarity/разный attr/weak/disjoint D24, excluded-middle fuzzy→OPEN, sufficient-reason accept premises) | done — `go test -race ./internal/facts` green |","| Совместимость с #56/#231: `go test ./internal/facts` + `bin/facts/audit.go self` зелёные (CI-гейт) | done |","| Документация: docs/facts/audit-recipes.md §Формальные проверки URL (L-9.3 #232) + PLAN.md | done |","","Verification: `go test -race ./internal/facts/` green, `go vet ./internal/facts/` clean,","`./bin/facts/audit.go self` ok. Branch `feat/facts-formal-checks#232` off `main`.","","## 2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)","","Goal: заполнить Commit/Person/AUTHORED (InitSchema write.go:130-133 уже создаёт","таблицы, писателя не было) из git-истории репо git.produktor.io — источник","фактов «кто над чем работал, когда» для аудита L-9 и оси проекта сети #234.","Дизайн: [docs/brain/graph-git-commits.md](docs/brain/graph-git-commits.md).","","| Item | Status |","|------|--------|","| Канон git-коммита: `(repo, sha)`, `Commit.id = \u003crepo\u003e:\u003csha\u003e` (repo = RepoName origin-remote); чтение — `gitlog.Log` (go-git, тот же канон, что premises-корпус: merge пропущены, автор = Author, дата RFC3339) | done |","| `internal/gitgraph` (cgo-free): Resolve (root-скан/--repo), ReadAll, ToInputs (email lowercase — Person.id канон mail D-1 #257, dedup, сортировка по дате ASC), ComputeStats, WeakSubject | done |","| `internal/brain/commitplan.go` (cgo-free): CommitInput, MERGE-запросы commit/AUTHORED, commitAuthor (Person.id=email); `gitcommit.go` (cgo): UpsertCommit/UpsertCommits (транзакция, идемпотентно) | done |","| CLI `bin/git/graph.go`: dry-run/--commit, --repo/--root/--since/--limit, --skip-existing, live-holder guard, батчи; отчёт before→after Commit/Person/AUTHORED | done |","| CLI `bin/git/facts.go` (read-only): факт-карточки «кто работал над repo когда» + audit card (claim/premises/inference/gaps/verdict; слабый subject → weaken+OPEN) | done |","| TDD: юнит cgo-free (commitplan + gitgraph: канон/маппинг/dedup/Resolve/ReadAll/WeakSubject/GroupFacts/BuildFacts) + cgo на живой Ladybug (temp DB): запись/перечитывание, повтор = 0 дублей, Person mail↔git сопряжение, сосуществование с Leaf/Message | done |","| Документация: docs/brain/graph-git-commits.md + шаблон audit card в docs/facts/audit-recipes.md | done |","| Live-пилот: 2dph + gator dry-run → --commit в kb.lbug (бэкап, live-holder guard), idempotency-повтор, git-facts спот-чек | done |","","Verification: `go test ./...` + `go vet ./...` green (CI-режим);","`bin/cgo/zig go test -tags system_ladybug ./internal/brain/` green (весь пакет);","`bin/cgo/zig go vet -tags system_ladybug ./internal/brain/` green.","Branch `feat/git-commits#233` off `main`.","","## 2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)","","Goal: инструмент сети поверх готового графа (mail D-1 #257 + git L-9.4 #233):","Person↔Person по общим письмам/тредам/получателям (SENT/TO/CC/BCC/REPLY_TO) и","общим проектам (AUTHORED Commit.repo), с premises (Message.id/Commit.id) и","verdict по audit-картам (accept/weaken); экспорт accept в CRM (ADR-0012 п.4).","Read-only: ничего не пишет, схему не меняет. Дизайн:","[docs/brain/graph-network.md](docs/brain/graph-network.md).","","| Item | Status |","|------|--------|","| `internal/network` (cgo-free): BuildLinks — деривации mail (прямой контакт sender↔recipient, вес TO/CC/BCC, общий получатель, REPLY_TO-диалог, треды) + git (общий проект по AUTHORED Commit.repo), premises, период, verdict accept/weaken+OPEN, ранжирование | done |","| Read-only запросы графа `internal/network/query.go` (cgo): LoadRows — Message/Person/рёбра SENT/TO/CC/BCC/REPLY_TO + Commit/AUTHORED → строки сети | done |","| CLI `bin/network/network.go` (read-only): `--person` (обязателен), `--project`, `--since/--until`, `--limit`, `--json`, `--accept-only` (CRM-экспорт YAML) | done |","| TDD: юнит cgo-free (деривации/вес/вердикты/ранжирование/premises на synthetic Alice/Bob/Carol) + cgo на живой Ladybug (temp DB): LoadRows→BuildLinks end-to-end | done |","| Live-пилот: eslider@gmail.com / andriy.oblivantsev@wheregroup.com на живой kb.lbug (read-only) — топ связей, premises, CRM-экспорт accept | done |","| Документация: docs/brain/graph-network.md + PLAN.md | done |","","Verification: `go test ./...` + `go vet ./...` green (CI-режим);","`bin/cgo/zig go test -tags system_ladybug ./internal/network/ ./internal/brain/`","green (cgo). Branch `feat/network#234` off `main`.","","## 2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)","","Goal: перенести conversation-канон ([#99](https://git.produktor.io/eSlider/2dph/issues/99),","`internal/canon`) в рабочую Ladybug-схему kb.lbug: узел **Message** + рёбра","**SENT/TO/CC/BCC/REPLY_TO** рядом с существующими Leaf/Person; идемпотентная","запись (MERGE). Дизайн: [docs/brain/graph-gator-mail.md](docs/brain/graph-gator-mail.md)","(D-1.1 #258). По итогу почта в 2dph живёт как граф сущностей, а не Leaf-текст","из сырья.","","| Item | Status |","|------|--------|","| `InitSchema` аддитивно (`IF NOT EXISTS`): `Message` node (id/thread_id/folder/subject/sent_at/gator_ref/body, PK id) + rel `SENT (Person→Message)`, `TO/CC/BCC (Message→Person)`, `REPLY_TO (Message→Message)`; Person/Leaf/Commit не тронуты | done |","| `internal/brain/graphplan.go` (cgo-free): `MessageInput` (canon.Message + folder/subject/gator_ref), `planGraph` — уникальные Person (From→To→CC→BCC, name первого вхождения), рёбра = `canon.Edges()` минус PART_OF/само-REPLY_TO; генерация MERGE-запросов (person/message/edge), `edgeSchema` | done |","| `internal/brain/graph.go` (cgo): `UpsertMessage`/`UpsertMessages` — пачка в одной транзакции, MERGE по message_id/email; Person name-мердж «последний sync побеждает» (SET в MERGE); gator_ref на Message | done |","| TDD: юнит cgo-free (план persons/edges, детерминизм, формы MERGE-запросов, sentAtText); cgo-тесты на живой Ladybug (temp DB): запись+перечитывание, повторный прогон = 0 дублей, name-мердж, сосуществование с Leaf + EnsureIndexes | done — 8 юнит + 4 cgo зелёные |","| Документация: §8 «Реализация D-1.2» в дизайн-доке (схема + write-путь) | done |","| Импорт gator kind=mail → граф (wheregroup 3984) | #260 (следующий) |","","Verification: `bin/cgo/zig go test -race -tags system_ladybug ./internal/brain/` green","(весь пакет, ~6 мин); `go vet ./...` + `go test ./...` green (CI-режим);","`go test ./internal/canon/` — 14 тестов green (canon #99 не изменён).","Branch `feat/graph-schema#259` off `main` (cc40235).","","## 2026-08-18 — Go-only write path sync (gitea #41)","","Goal: consolidate the most advanced version here and in sync with GitHub main.","Sources: GitHub main `b1e6953` + arc-1 `/mnt/8TB/projects/ai/2dph` Go-write WIP +","edelweiss `/home/devops/projects/2dph` M365 WIP. Prefer Go; Python only for A/B.","","| Step | Status |","|------|--------|","| sync issues via tea (gitea #41 assigned) | done |","| Phase 1: branch `feat/brain-go-writepath` + arc-1/edelweiss WIP | done |","| Phase 2: finish Go write path (write.go/corpus.go/index/add/mailconv) TDD | done |","| yaml-seed secret/noise filter; compose/CI → Go (no Python INSTALL) | done |","| Phase 3: PR → CI green → merge GitHub main; docs (D6 reversal) | done — PR #42, merged `2cea0e7` |","| Phase 4: go-ollama + go-xls PR/merge; verify go-env/go-hocr/go-onlyoffice | done — PR #4 (`325c4e2`), PR #1 (`165d25d`) |","| Phase 5: deploy brain :8630 + wire MCP into opencode.json | done — brain live (7754 leafs), MCP wired; PR #43 merged `a1880de` |","","Note: CI found + fixed 3 real bugs in the port (modeldir HF-cache default +","snapshot weight selection, secret/noise filter false positives).","","Deploy findings (PR #43): fts-extension install needs writable+execable HOME","(`/data`, not noexec `/tmp`); serve must bind `KB_HOST=0.0.0.0` for compose","publishing; corpus walk must skip permission-denied entries; leaf text needs","UTF-8 sanitization before FTS.","","Decisions (2026-08-18): exclude arc-1 junk (external/, docs/chats/, bin/chats.bin,","qa/load_test_*.py); Python fully removed (2026-08-19); M365 WIP same PR;","go-ollama+go-xls now, rest verify.","## 2026-08-22 — Typed config (gitea #90, epic #88)","","Goal: replace ad-hoc `os.Getenv` in the service layer with one typed config","loaded by a stack, keeping legacy env names working. Dep","`github.com/eslider/go-config@v0.4.0`; new `internal/config` (`config.go`,","`load.go`, `yaml.go`, `env.go`) — TDD offline fixtures, no network/db.","","| Step | Status |","|------|--------|","| internal/config load stack + merge + lower+alnum + legacy mapping (TDD) | done — tests green |","| wire pkg/httpapi, internal/brain, bin/brain/{serve,index,watch}, internal/mailsync, internal/websearch | done — no ad-hoc env reads in those packages |","| committed `etc/brain/config.yml` template + gitignore `config.local.yml` | done |","| legacy env map | KB_ROOT→Root, KB_PORT→Port, KB_HOST→Host, KB_WORKERS→Workers, KB_PPROF→Pprof, KB_SEARCH_CMD→SearchCmd, KBSEARCH_PORT→SearchDaemonPort, KBSEARCH_NO_DAEMON→SearchNoDaemon, KBSEARCH_MODEL→Model, HF_HOME→HFHome, KB_BUFFER_POOL→BufferPool, KBTEST_EPS→Eps, KB_INDEX_ALLOW_LIVE→IndexAllowLive, KB_WATCH_INTERVAL→WatchInterval, KB_WATCH_DIRS→WatchDirs, OO_CLI→OOCLI, BRAIN_SEARCH_{URL,USER,PASS,CACHE,ENV}→Search.* |","","Non-goals kept: graph/search semantics unchanged. Pending: OnlyOffice CLI","config wiring from `bin/mail/*` (out of #90 scope) — `internal/mailsync.SetOOCLI`","is ready for it.","## 2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)","","Goal: replace `jhillyerd/enmime/v2` with `emersion/go-message` in","`internal/mailconv` (parser-equivalent swap, no behaviour change to graph/search).","","| Step | Status |","|------|--------|","| fixtures: plain / alternative / mixed / nested(alternative+mixed) / charset(iso-8859-1 QP) `.eml` in `internal/mailconv/testdata/` | done |","| `parseEML` via `message.Read` + `entity.Walk()` recursion; text/plain→TextBody, text/html→HTMLBody, attachment leaves by filename/disposition | done |","| charset via `go-message/charset` (x/text ianaindex/htmlindex) — blank import | done |","| public API stable: `FromEML`/`FromRaw`/`ConvertAttachment`/`Message`/`Attachment` unchanged; `writeMessageJSON`/`writeEMLAttachments` internal | done |","| `go mod tidy` → enmime absent from go.mod | done |","| `go build ./internal/... ./pkg/...` + `go test -race ./internal/mailconv/... ./pkg/...` green | done |","| -benchmem before(env v2.4.1)/after(emersion v0.18.2): ~6–9× faster ns/op, B/op ≈ half, allocs ≈ 40–55% lower | done — bench in `eml_test.go` |","","Branch `feat/mime-emersion#95`: c8f7120 (test) → 7015ffc (feat) → 9b4425d (chore/deps).","## 2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)","","Goal: small defects found porting mailsync v1 — `mailconv.FromEML` folder","extraction and the Dockerfile `pkg/` copy.","","| Step | Status |","|------|--------|","| test (red): `TestFromEMLFolderNestedLayout` — `\u003croot\u003e/\u003cfolder\u003e/\u003cid\u003e/\u003cid\u003e.eml` asserts folder=`\u003cfolder\u003e` via `message.json` + `message.md` | done |","| `emlFolder(root,p)`: parent of the `\u003cid\u003e` dir (grandparent of `.eml`) for the nested layout; flat `\u003croot\u003e/*.eml` keeps immediate dir; applied in `FromEML` + `writeMessageJSON` | done |","| Dockerfile `pkg/` copy: release/v1 root `Dockerfile` already `COPY . .` (covers `pkg/cli` for `internal/ocr`); no `deploy/mail-watcher.Dockerfile` in release/v1 (that file lives on main via #107, not an ancestor) — no change needed | already-fixed (verify only) |","| `go test ./internal/mailconv/... ./internal/mailsync/... ./internal/ocr/...` + `./internal/...` green; `gofmt -l` clean; `go vet` clean | done |","","Branch `fix/mailsync-v1#111` → PR into `release/v1`.","## 2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)","","Goal: land the v1 generic IMAP source (from #106) that was never merged:","`internal/mailsync/{imap,watch}.go` + offline tests + CLI/flags wiring +","`bin/mail/watch` entrypoint + `deploy/mail-watcher.Dockerfile`.","","| Step | Status |","|------|--------|","| delta audit vs release/v1 (756f514): missing = imap source files, watch loop, CLI flags (`--once/--brain/--ocr/--local`), `IMAP_*` env parsing, `SyncConfig.IMAP`, `bin/mail/watch`, `deploy/mail-watcher.Dockerfile`, `go-imap/v2`+`go-sasl` deps | done |","| overlap check: brain write-path fixes (`internal/brain/*`, `mailconv.FromEML`) already on release/v1 via #109/#110/#111 — NOT re-ported; old `gmail.go`/`m365.go`/`onlyoffice.go` untouched and compiling | done |","| port: `internal/mailsync/imap.go` + `imap_test.go` + `watch.go`, cli.go/sync.go wiring, `bin/mail/watch/main.go`, Dockerfile, `go.mod`/`go.sum` | done |","| source selection: `--source imap` → `IMAPEnv(envVars)` → `SyncConfig.IMAP` → `newIMAPSources`; `cfg.Raw=true` (always `.eml`); readEnv honours `IMAP_*` | done |","| tests (offline, synthetic Alice/Bob): `SanitizeFolder`, `IMAPEnv`, `parseIMAPMessage` (emersion parser), `hasNoSelect`; no network | done |","| `go test ./internal/...` + `./internal/mailsync/...` green; `go vet` clean; `gofmt -l` clean (shebang-protected files excluded) | done |","| branch `merge/imap-mailsync-v1#112` off `origin/release/v1` → PR into `release/v1`, CI green | done (open, not merged) |","","Branch `merge/imap-mailsync-v1#112` → PR into `release/v1`.","","## 2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)","","Goal: apply `skills/api-client/SKILL.md` (go-ollama canon) to","`internal/reasoner/client.go`, first candidate. Removes the 16× `map[string]any`","(typed `Tool`/`ToolSchema`).","","| Step | Status |","|------|--------|","| test: offline httptest typed client — chat round-trip, tool-call, error/status, ctx cancel, NDJSON doStream | done — `-race` green |","| feat: `Client{hc,cfg}` + `New(cfg)`, `doJSON`/`doStream` core, typed `ChatRequest`/`ChatResponse`, ctx-first | done |","| config: `reasoner.Config` + `LoadEnv()` accessor; bench.go reads `REASONER_BASE_URL` from it | done |","| bench.go: typed client, `ctx`; bake-off behaviour preserved | done |","| `map[string]any` in `internal/reasoner/client.go` → 0 | done |","| PLAN.md #93 status | done |","","Notes: rebased onto `release/v1` `36f5ec4` (after #90 landed). `internal/config`","carries no reasoner fields, so a minimal `reasoner.LoadEnv()` accessor (env →","`Config`) is kept; wiring `REASONER_BASE_URL` into `internal/config.Config` is","deferred. Bench shebang is `gofmt`-protected (not reformatted).","## 2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)","","Goal: extend `internal/config` to the remaining tools/packages, add a `reasoner`","section and retire the ad-hoc `os.Getenv` config reads (KB_*, BRAIN_SEARCH_*,","REASONER_*, HF_HOME, model, port/workers…).","","| Step | Status |","|------|--------|","| `config.ReasonerConfig{BaseURL,Model,Device}` + `Config.Reasoner` section + defaults | done |","| legacy map: REASONER_BASE_URL→Reasoner.BaseURL, REASONER_MODEL→Reasoner.Model, REASONER_DEVICE→Reasoner.Device | done |","| reasoner client config from typed stack: `reasoner.Configure(cfg)` + `FromTyped`; `LoadEnv()` reads `active` | done — `bin/reasoner/bench.go` loads `config.Load` + `Configure`; client tests green |","| `bin/web/search.go`: BRAIN_SEARCH_CACHE/ENV → `cfg.Search.Cache/Env` (HOME fallback kept) | done |","| `bin/brain/model.go`: HF_HOME/KB_ROOT → `cfg.HFHome/cfg.Root` | done |","| `etc/brain/config.yml` template gains `reasoner:` section | done |","| TDD fixtures: `reasoner` deep-merge in `testdata/basic`, legacy + defaults tests, `isolateEnv` covers REASONER_* | done — `go test -race ./internal/... ./pkg/...` green |","","Notes: `internal/chat/paths.go` KB_ROOT + `pkg/repo/exec.go` KB_ROOT left as","ad-hoc env reads — chat sources are secret-heavy (no config accessor yet) and","`pkg/` must not import `internal` (overlaps #92 `pkg/utils`). Bench/web shebangs","are `gofmt`-protected (not reformatted).","","## 2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)","","Goal: promote duplicated helpers into public `pkg/utils` with static typing and","kill the `map[string]any` / `interface{}` HTTP and root-resolution hotspots.","`pkg/utils` stays internal-free (D29, primitives only).","","| Step | Status |","|------|--------|","| `pkg/utils/root.go` `Root()` — KB_ROOT or walk-up `.git`/`var`, single source of truth; `pkg/repo.Root()` and `internal/chat.Root()` delegate | done |","| `pkg/utils/json.go` `DoJSON`/`GetJSON` — typed request + 2xx check + decode, `opts` for auth; wired into `internal/reasoner.Client.doJSON`, `internal/websearch.Fetch`, `internal/chat.cdpTabs` | done |","| `pkg/utils/str.go` `Snippet`/`Or` — `reasoner.snippet`, `chat.orDefault` removed | done |","| TDD fixtures: `pkg/utils/{root,json,str}_test.go` offline (httptest, t.TempDir) | done — `go test -race ./pkg/... ./internal/...` green, `go vet` clean, CGO ladybug build green |","","Notes: `pkg/httpapi` write-side (`writeJSON`/`writeRaw`) is a different direction","(server response, not client decode) and was left as-is. Remaining","`map[string]any`/`interface{}` decode sites in `internal/brain`, `internal/chat`","(MCP/JSONL) and `internal/config` are domain-shaped and out of scope for this","pass.","## 2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)","","Goal: kill the silent in-process Ladybug death on `POST /ingest` (~10% of","writes, ExitCode=0, no Go panic) and prove it with a soak test.","","Root cause (release/v1): `HTTP.Ingest` opened a **second** handle on the live","`kb.lbug` (`OpenWritable`) while the serve read handle stayed open — liblbug","dies silently when the same file is double-opened inside one process. The","existing close→reopen swap (`refreshBrain`) also ran unsynchronized against","concurrent handlers: any reader mid-query could execute a statement against a","just-closed `*lbug.Connection` (C use-after-close; frame","`LocalNodeTable::isVisible ← HashIndex::lookupInPersistentIndex`). Error paths","left the serve handle stale for the process lifetime.","","| Step | Status |","|------|--------|","| `brainMu sync.RWMutex` guards global db/conn; readers (Get/Stats/Audit, queryFTS/queryVector/attachHops, lookupLeaf/leafStats) hold RLock | done |","| Ingest serialized (`ingestMu`), holds write lock over close→write→reopen; `closeBrainLocked()` runs BEFORE `OpenWritable()` (no same-file double-open) | done |","| Restore on every exit path (`ingestWriteLocked` + deferred restore): write-stage errors no longer leave a stale/nil serve handle | done |","| `embedIngestLeafs` seam — embed step extracted as package var, tests run full Ingest offline (no HF model/daemon); singleton-cache (#110) replaces only its body | done |","| Soak: `TestIngestSoakWritePath` 200 × real `/ingest` + 8 concurrent readers (Stats/Audit/Get/FTS/vector), temp KB_ROOT, offline | red before fix (native segfault), green after |","| Race proofs: `TestIngestSwapRaceSafe`, `TestIngestWriteRestoresServeConnOnError` | green under `-race` |","","Verify: `bin/cgo/zig go test -race -tags system_ladybug ./internal/brain/`","green; `go vet` clean; zig build of `bin/brain/search.go` OK. Same-file","double-open stays forbidden inside one process; bulk rebuild keeps its own","process (`bin/brain/index.go`). Out of scope: corpus CLI path (single-goroutine","index process), model singleton cache (#110).","","## 2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)","","Goal: base layer for sync-ETL. A concurrency-safe registry of per-format ETL","handlers plus a hardened file walker (path traversal / symlink-escape /","depth-limit / binary-huge-file skip). Runner (#98) and source-adapters (#97)","build on these.","","| Step | Status |","|------|--------|","| `internal/etl/registry.go`: `Registry` (RWMutex) mapping handler key → `Handler{Name() string; Handle(ctx, path) error}`; `Register` rejects duplicate/empty keys, `Lookup`/`Names`(sorted)/`Len` — concurrent-safe | done |","| `internal/etl/walker.go`: `WalkFiles(root, WalkOptions{MaxDepth, MaxBytes, SkipBinary, Exts})` → deterministic sorted `[]File{Path,Rel,Size}`; rejects `..` traversal, never follows symlinked dirs, rejects symlink escapes outside root, honors depth limit, skips huge/binary files | done |","| `internal/markdown.WalkMarkdown` refactored to the hardened walker (Exts filter) | done |","| TDD: `internal/etl/etl_test.go` — register/lookup/dup/sorted/concurrent(-race), deterministic order, traversal rejected, symlink-to-outside rejected, depth limit, large-file skip, binary skip, ext filter, missing-root error | done — `go test -race ./internal/...` green, `go vet` clean, gofmt clean |","","Notes: scope kept to registry + walker only; pipeline rewiring is #97/#98.","`internal/mailconv` and `bin/*` walkers stay legacy until a source adapter owns","them (single-implementation rule D33) — cutover tracked in #98.","","## 2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)","","Goal: unified `Source` adapter layer + the bounded pipeline runner that wires","`Source → Decode(Registry) → Transform → Load` with backpressure, per-run stats","YAML and graceful shutdown.","","| Step | Status |","|------|--------|","| `internal/source`: `Source{Name(); Fetch(ctx,cursor)→[]Blob,Cursor,error}`, `Blob{ID,Kind,Path}`, `Cursor`; `Sync(ctx,src,handle,Options{StatePath})→Stats{Fetched,New,Skipped}` — sha256 seen-set dedup + atomic checkpoints, mid-batch resume | done (#97) |","| `source.Disk`: re-lists a corpus dir of `.eml`, content-hash identity, cursor never advances (driver dedups) | done (#97) |","| `internal/runner`: bounded channels between stages (backpressure), `errgroup.WithContext` first-error cancel, wg-accounted goroutines, graceful shutdown; reuses `source.Sync` + `etl.Registry` | done |","| Runner `Report`: source stats + per-handler ok/failed counts, serialized as stats YAML (`startedAt/duration/source/handlers`) | done |","| `bin/runner/run.go`: CLI daemon over the disk corpus; `mail` handler → `mailconv.FromEMLFile` (per-blob .eml→md); `--interval` loop + SIGINT/SIGTERM graceful shutdown | done |","| Compose `mail-sync` migrates to the runner (`command: [\"runner\",\"300\"]`); `MAIL_SYNC_SRC/ENV/INDEX` semantics preserved (`INDEX=1` = full brain rebuild); download stays on the existing mailsync binary (source-adapter mapping for onlyoffice/gmail = follow-up) | done |","| TDD: `internal/runner/runner_test.go` — e2e disk source smoke (stats YAML round-trip), bounded in-flight backpressure, graceful shutdown on cancel (-race), unknown-kind error; `internal/mailconv` `FromEMLFile` idempotence | done — `go test -race` green, `go vet` clean, `docker compose config` parses |","","## 2026-08-22 — singleton-cache ingest model (gitea #110)","","Goal: kill the ~0.9 GiB/`/ingest` leak. `HTTP.Ingest` called `LoadModel()` per","request and `defer model.Close()`; `StaticModel.Close()` frees only the","tokenizer, not the safetensors matrix (measured 3 ingests: RSS 3.5→6.1 GiB;","~900-email backfill = OOM).","","| Step | Status |","|------|--------|","| `internal/brain/ingestmodel.go`: `embedder` interface (no Close — structural guarantee), `ingestModelCache` (mutex, failed loads not cached → retry next request), `getIngestModel()` process-wide singleton, `embedLeafs()` extracted from Ingest | done |","| `internal/brain/http.go` Ingest: `LoadModel()+defer Close()` → `getIngestModel()` + `embedLeafs`; shared model never closed per request | done |","| TDD: `internal/brain/ingestmodel_test.go` — single load across consecutive requests, 16-goroutine concurrent load-once, zero Close of shared model, retry-after-loader-error, pre-embedded skip | done — `zig go test -race -tags system_ladybug ./internal/brain/` green, `go vet` clean both tag modes, CGO search build green |","","Notes: other `LoadModel()` callers (`bin/brain/add.go`, `index.go`,","`bin/facts/*`) are one-shot CLI processes — unchanged. Diff kept strictly to","model caching; connection lifecycle in the same function is #109 territory.","","## 2026-09-23 — shared embedding model on the search path (gitea #335)","","Goal: same fix as #110, extended to `/search`. `internal/brain/search.go`","`embedQuery` still called `loadModel()` + `defer model.Close()` on every","request, and `StaticModel.Close()` freed only the tokenizer — so each search","retained ~490 MiB inuse (~980 MiB allocated) until GC, and a live `brain-serve`","grew to 13 GiB (RSS 1.8 + swap 11.2) over the DB's 11 MB.","","| Step | Status |","|------|--------|","| `internal/brain/embedmodel.go` (renamed from `ingestmodel.go`): `embedder` (no Close), `embedModelCache`, `getEmbedModel()` process-wide singleton; now backs /ingest, the /search in-process fallback AND ANN extract | done |","| `search.go` `embedQuery` → new `embedInProcess()` (cache, never Close); `serve()` (daemon) uses the shared model without closing; `anntool.go` `annExtractEmbed` drops its private `sync.Once` for the shared cache | done |","| `model.go` `StaticModel.Close()` now releases `mat` too (one-shot CLI callers); shared instance is never closed | done |","| `pkg/httpapi` pprof binds `127.0.0.1` instead of the API host (was `0.0.0.0:1707` via KB_HOST) | done |","| `compose.yaml`: `KB_BUFFER_POOL` 4 GiB → 512 MiB, `deploy.resources.limits.memory: 2g` (container had `HostConfig.Memory=0`) | done |","| TDD: `internal/brain/embedmodel_test.go` — 200 sequential search embeds load once and never Close; real `embedQuery` fallback loads once; `Close` frees the matrix; plus the #110 cache tests | done |","","Live before/after (same host, `KBSEARCH_NO_DAEMON=1`, sequential","`test/stress --mix search:1 --c 1`): before, 13 searches grew RSS 147 MiB →","6.8 GiB (swap 0.5 GiB) with `alloc_space` `loadMatrix` 13.84 GiB; after, 49,448","searches kept `alloc_space` `loadMatrix` at 977 MB (one load), p50 6 ms vs","1429 ms, RSS post-warmup delta ~36 MiB (\u003c 50 MiB), swap flat. See `PERF.md`.","","## 2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)","","Audit of the concurrency paths outside `internal/brain` db/conn lifecycle","(#109/#110 `brainMu`), driving the `internal/mailsync` worker pools, the","`corpuswatch` poll loop, the `bin/brain/index.go` bulk rebuild and the","`bin/reasoner/bench.go` bake-off. Goal: data races, unbounded concurrency,","unsynchronized shared state, goroutine leaks — hardening only, no semantic","change. Result per component (all verified with `go test -race`):","","| Component | Verdict | Hazard(s) found | Fix |","|-----------|---------|-----------------|-----|","| `internal/mailsync` `Run` worker pool + gmail/m365/onlyoffice sources | clean | none — counters already atomic, failures slice already under mutex, m365 token already channel-mutexed, bounded workers, no leak | none (test seam + race test only) |","| `internal/corpuswatch` poll loop | clean | none — single-goroutine loop, `Stamp` is pure filesystem walk, no shared state | none (race test for `Stamp` concurrency) |","| `bin/brain/index.go` → `WriteCorpus` → `parallelEmbed` + `ProgressReporter` | **hazard fixed** | `ProgressReporter.total` was a plain `int` written from embedding worker goroutines (`Report`) and read under the mutex in `render` → data race | `total` → `atomic.Int64` (`internal/brain/corpus_pool.go`) |","| `bin/reasoner/bench.go` → `internal/reasoner` `Client.Run` | clean | none — client holds only `*http.Client` + immutable `Config`, `Run` keeps accounting in call-local state | none (race test for concurrent bench runs) |","","| Step | Status |","|------|--------|","| `internal/brain/corpus_pool.go`: `ProgressReporter.total int` → `atomic.Int64` (race on concurrent `Report`) | done |","| TDD: `internal/brain/progress_race_test.go` — 16-goroutine concurrent `Report` + `Finish` under `-race`; total converges | done |","| TDD: `internal/mailsync/sync_race_test.go` — 8-worker `Run` over a fake `Source` under `-race` (test-only `sources` seam, no prod behavior change) | done |","| TDD: `internal/corpuswatch/stamp_race_test.go` — 16-goroutine concurrent `Stamp` under `-race` | done |","| TDD: `internal/reasoner/client_race_test.go` — 12 concurrent `Client.Run` + 16×10 shared-client `RunPrompt` under `-race` vs httptest | done |","","Verification: `go test -race -count=1 ./internal/...` green; `go vet ./internal/...`","clean; touched files gofmt-clean; cgo Zig build of `bin/brain/search.go`","(`-tags system_ladybug`) green. Branch `audit/concurrency#94` off `release/v1`.","","## 2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)","","Goal: stable content addressing for the ETL graph. Every extracted node gets a","canonical URL `scheme://platform/thread/msg/path-segments[#anchor]`, node ID =","`sha256(url)[:16]`, separate content ID = `sha256(body)`; parts reference each","other only via URLs. Content splits granularly BEFORE insertion into typed","Items (paragraph/heading/table/row/cell/image/link/page); a DOM/jQuery-like","selector mini-language addresses any leaf. Scope: addressing + selector +","split helpers with proof-of-use tests. Graph wiring (#99/#101, Ladybug) and","format coverage beyond html/md are out of scope here.","","| Step | Status |","|------|--------|","| `internal/address`: `Segment` (parse/render `type[index]`), `New`/`Parse` canonical URL (validated scheme/platform/thread/msg/segments/anchor), `NodeID(url)=sha256(url)[:16]` (32 hex), `ContentID(body)=sha256` (64 hex) | done |","| `internal/selector`: mini-language `p[3] \u003e table[0] \u003e tr[1] td[2]` (`\u003e` direct child, space descendant, optional index = any) — `Parse` (strict grammar errors) + `Apply(root)` over a typed Item tree | done |","| `internal/items`: `Kind` named int enum (page→`body`/heading/p/table/tr/td/img/a), typed `Item{Kind,Seg,Path,URL,Body,Src,Alt,Href,Children}`; `SplitHTML` (via `golang.org/x/net/html`) and `SplitMarkdown` (pipe-tables + standalone link/image) assign per-type indices and canonical URLs; content split happens BEFORE insertion | done |","| TDD: `internal/address` round-trips + validation + id derivation; `internal/selector` parse/apply + edge cases (bad grammar, missing index, no-match); `internal/items` html/md fixtures (testdata/); `internal/items/e2e_test.go` — split→select→verify URL+node/content id, parsed back to same leaf | done |","","Verification: `go test -race -count=1 ./internal/...` green; `go vet ./internal/...`","clean; new packages gofmt-clean. Branch `feat/content-url-addressing#100` off","`release/v1`. No end-to-end graph write (that's #99/#101 + Ladybug).","","## 2026-08-23 — secret-scan в CI всех репо (gitea #142)","","Goal: не допустить новых утечек секретов на этапе PR/push (после #140 — 8 HIGH-файлов в matrix).","","| Step | Status |","|------|--------|","| Выбор инструмента: **gitleaks** (официальный образ `zricethezav/gitleaks`) — diff/commit-скан, fail-on-leak, generic+private-key правила | done |","| CI job `secret-scan` (pull_request + push, сканирует только diff новых коммитов) в 2dph (ci.yml), go-onlyoffice (test.yml), go-config (secret-scan.yml), matrix (.gitea/workflows/secret-scan.yml) | done — PR 2dph#143, go-onlyoffice#5, go-config#1, matrix#6 |","| git pre-push + pre-commit хуки `scripts/githooks/` (+ install.sh), блокируют push/commit при находке | done |","| Скилы git-workflow + devops: правило «скан перед любым push»; синхронизированы в ai-fabric (release/v2) | done |","| Верификация: скан ловит реальные утечки matrix #140 (14 findings, exit 1); pre-push блокирует push с секретом | done |","","## 2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)","","Goal: единая каноническая модель для почты и чатов — `Message{ID,ThreadID,Platform,","From,ReplyTo,To,CC,BCC,SentAt,Body,Attachments(lazy)}`; хранение на диске под","`var/corpus/{mail,chats}` как JSON + манифест sha256 (идемпотентный upsert); граф","`(:Person)-[:SENT]-\u003e(:Message)-[:TO|CC|BCC]-\u003e(:Person)`, `[:REPLY_TO]`, `[:PART_OF]`","параграфы. Первый mergeable инкремент: ядро схемы + диск + манифест + round-trip","и golden-тест (один `.eml` и один telegram-экспорт дают одинаковую схему графа).","","| Step | Status |","|------|--------|","| `internal/canon` (evidence-слой, пакет-домен): `Person`/`Attachment`(lazy `Open(ctx)`, без буферизации тела)/`Message`/`Edge`/`EdgeType` (SENT/TO/CC/BCC/REPLY_TO/PART_OF), `Edges()`, `GraphSchema()` | done |","| `Store{root}`: `Write` (идемпотентный upsert по sha256 тела), `Read` (скипает битые файлы), `LoadManifest`/`SaveManifest` (атомарный); layout `root/\u003cplatform\u003e/\u003cthread\u003e/\u003cid\u003e.json`; сегменты путей через `pkg/utils.SafeSegment` | done |","| `FromMail(r)` на emersion/go-message: envelope (From/To/Cc/Bcc через mail.Header.AddressList), ThreadID из References/Message-Id, ReplyTo из In-Reply-To, тело text/plain (fallback text/html → mailconv.StripHTML), вложения lazy (метаданные) | done |","| `FromChat(platform,thread,text,from,ts,replyTo)`: Person id = `platform:handle`, парсинг ts (RFC3339), ReplyTo-цепочка | done |","| TDD (offline, фикстуры synthetic Alice/Bob/example.com): round-trip write→read→canon для mail и chat; идемпотентный повторный write; манифест = sha256(JSON); layout; Edges/схема; golden — mail и telegram дают одинаковую `GraphSchema()` | done — 13 тестов, `-race` зелёные |","| docs/PLAN.md: секция #99 (этот блок) | done |","","Осталось (вне первого инкремента): конверторы существующих источников (gmail/onlyoffice","`mailconv.Message`, `mailsync.Message`, whatsapp/linkedin экспорт) → канон; запись графа","в Ladybug (`(:Person)/:Message/:Paragraph` узлы + `SENT|TO|CC|BCC|REPLY_TO|PART_OF`","рёбра) — будет отдельным инкрементом на #99; миграция `bin/*/import` на канон.","","Verification: `go build ./...` green; `go vet ./internal/canon/... ./pkg/utils/...`","clean; `go test -race ./internal/canon/... ./pkg/utils/...` green. Branch","`feat/conversation-canon#99` off `release/v1`.","","## 2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)","","Goal: из wave-1 письма уходили plain-text одной строкой; теперь брендированный","HTML-шаблон + гейт рендер-проверки, блокирующий send, пока черновик не пройдёт","assert (абзацы, подпись, logo cid, chat-line без дублей).","","| Step | Status |","|------|--------|","| `internal/oohtml` (новый пакет-домен): брендированный шаблон produktor.io (палитра #76: cream `#FAF5EA`, ink `#0A0A0A`, navy `#142039`/`#0E1626`, CTA `#F2C849`, primary `#143A6F`; system-ui; inline-стили; логотип `cid:produktor-logo.gif` без remote-URL) | done |","| `RenderCheck(html, TemplateData)` — типизированный ассерт: paragraphs ≥2, greeting, offer+CTA, signature+site, logo cid, отсутствие дублей текстовых блоков (`Code` enum) | done |","| `Mailer.Send(ctx, SendParams)` — TDD-гейт: `Build` → `SaveMailDraft`(HTML) → `GetMailMessage` back → `RenderCheck` → `SendMail` только при чистой проверке; иначе error «render check failed, send blocked» | done |","| `BuildMessage(w, …)` — MIME `multipart/related` через `emersion/go-message`: HTML-часть + inline `image/gif` с `Content-ID` = логотип (cid-embedding, не remote fetch) | done |","| Лого-ассеты `etc/onlyoffice/assets/produktor-logo.{png,gif,-64.gif}` | готовы |","| TDD offline (фикстуры synthetic Alice/Bob/example.com; fakeStore вместо сети): 10 тестов, `-race` зелёные | done |","","Verification: `go vet ./...` clean; `go test -race ./internal/oohtml/` green (10/10).","Branch `feat/ooo-mail-html-template#76` off `release/v1`. Осталось вне объёма:","wiring в OnlyOffice CLI (`oo mails send`) и фактическая отправка с вложенным","cid-логотипом через драфт-API OnlyOffice.","","## 2026-08-25 — CRM association two-source rule (gitea #55, epic #52)","","Goal: `bin/facts/prove-crm` (issue references it as `bin/facts/crm`) должен","доказывать person→company И company→project как `root=facts` только когда оба","источника (corpus SoT `eslider/cv/projects/knowledge-mesh-seed.yaml` × ooCRM","`graph.json`) согласны; односторонние — mismatch, никогда fact.","","| Step | Status |","|------|--------|","| merge-правило вынесено из `main()` в `internal/facts.CRMAssocFacts` (single implementation, rule #10): person→company (companies_with_persons) + company→project (projects_contacts) | done |","| фикс ложного совпадения: generic-токены (`client`, `gmbh`, `ag`, `systems`, `platform`, …) больше не двигают company-match (`significantTokens` stopwords) — «Markets Platform (client)» больше не матчит «Client not named yet» | done |","| дедуп mismatches (раньше employer-kind loop дублировал «not found») + отчёт CRM-only компаний и проектов без company | done |","| TDD offline на synthetic-фикстурах: two-source rule + все mismatch-пути + generic-token регрессия, `-race` зелёные | done |","","Verification: `go test -race ./internal/facts/` green (тесты crm/company-merge).","Против реального SoT (dry-run): proven=0; 9 corpus-org не в CRM; 4 CRM-only","компании (Capitole, Client not named yet, Teksystems, talabat); 198 проектов","без company — `projects_contacts[].companies` в `graph.json` пуст у всех","(данные заполняет внешний `graph.py`, вне репо). company→project пока недоказуем","из-за этого data-gap; person→company недоказуем — CRM-компании с персонами не","совпадают с corpus orgs. Branch `feat/crm-assoc#55`.","","## 2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)","","Goal: prove the `search → get → audit` operator flow on ONE known deal and the","contradiction-as-`(not confirmed)` path (D16), offline with synthetic fixtures.","","| Step | Status |","|------|--------|","| offline test: `search` (fuse FTS+vector, root filter) → `get` (leaf by id → source/confidence) → `audit` (`facts.CheckFactRow` + `facts.Adjudicate`) confirms two-source facts on synthetic deal `acme-2026` (Alice/Bob/example.com) | done |","| offline test: 2v2 contradiction leaf (`a x b vs c x d`, no rule fires) stays `(not confirmed)`/hypothesis until audited; never a confirmed fact; deduction escalates | done |","| operator flow documented for bot/assistant (`search`/`get`/`audit`; confirmed vs `(not confirmed)`) — `docs/facts/audit-recipes.md` | done |","","Verification: `go vet ./internal/facts/... ./internal/brain/rank/...` clean;","`go test -race ./internal/brain/rank/ ./internal/facts/` green","(`TestDealSearchGetAuditConfirmedFlow`, `TestDealContradictionNotConfirmedUntilAudited`).","Блокер #53 закрыт; live `get` против `var/kb.lbug` + OnlyOffice CRM остаётся","вне объёма (offline-план доказывает cgo-free логику). Branch `test/facts-lookup#58`.","","## 2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)","","Goal: the production brain had **0 facts-root** leafs — all 9204 in `info`","(9201 confirmed). Root cause: `bin/brain/index.go --rebuild` was run without","`--with-facts`; `internal/brain/corpus.go` writes every corpus leaf as","`root=info` (single file source), and the 2-source facts layer is a separate","`bin/facts/extract.go` stage. Audit: all 9201 confirmed info leafs are","single-source corpus leafs → **0** qualify for re-rooting by the ≥2-sources rule.","","| Step | Status |","|------|--------|","| `internal/brain` facts-promotion core: `SourceCount` (source string, `\" x \"` separator), `PromoteEligible` (≥2 sources + confirmed → facts), `PromoteFacts`/`EligibleInfoLeafs` (DB scan, idempotent upsert) | done |","| TDD tests: `TestSourceCount`, `TestPromoteEligible`, `TestPromoteFactsPromotesAndIdempotent`, `TestPromoteFactsReadPathDistinguishes` — `go test -race ./internal/brain/` green | done |","| `bin/facts/promote.go` CLI (`--dry-run`, `--db`); `bin/facts/extract.go --dry-run` = 18 two-source facts | done |","| live DB: writes blocked (var/ owned 1001, no sudo; in-service `/ingest` bulk write hung the C layer → container restart). Full ingestion demonstrated on a faithful copy: before facts=0 info=9204 → after facts=18 info=9204 | done |","| docs: `docs/facts/ingest.md` procedure + live runbook; `docs/brain/rebuild.md` facts note | done |","","Verification: `go vet ./internal/brain/ ./bin/facts/` clean;","`go test -race ./internal/brain/ -run 'TestSourceCount|TestPromoteEligible|TestPromoteFacts'` green.","Live write to `var/kb.lbug` requires the brain stopped + DB-owner (uid 1001/root)","write access — procedure in `docs/facts/ingest.md`. Branch `feat/facts-ingest#181`.","","## 2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)","","Goal: закрыть гэп импорта дисковых источников почты/контактов на /mnt/8TB,","которые не были в пайплайне (#68/#74 импортировали только var/mail). Wave-1","(#84) уже импортировал mbox/.eml: tb-andriy-profile 35,368 · tb-backup-128g","39,435 · tb-2010-zip 1,289 · contacts-eml 125 (Krylov исключён). Этот шаг —","вынести mbox-сплиттер в тестируемый слой + доказать импорт тестами.","","| Step | Status |","|------|--------|","| инвентарь источников подтверждён на диске (read-only): TB-профиль Mail/ImapMail, оба PST (265 KB), Backup E-Mails, zip-2010, VCF/MAB — таблица-истина в `docs/mail-sources.md` | done |","| mbox-сплиттер вынесен из build-tag скрипта в `internal/mailconv` (single implementation): `SplitMbox`, `SplitMboxDir`, `MboxMessage`, `LooksMbox`; `bin/mail/convert-mbox.go` — тонкая обёртка | done |","| TDD: `TestSplitMbox`, `TestMboxMessage` (mbox→Message + source-тег), `TestSplitMboxDirIdempotent` (повторный прогон без дублей, контент-адресация sha256:16), `TestFromEMLFlatRoot` (contacts-eml плоский root) — `go test -race ./internal/mailconv/` зелёные | done |","| PST: `readpst` НЕ установлен → источник остаётся ЗАБЛОКИРОВАН; путь закрытия (libpst-utils → readpst -e → .eml → FromEML) задокументирован, не подделан | done |","| docs: `docs/mail-sources.md` (таблица-истина), runbook-секция, конфиг/abs-путей в коммитах нет | done |","","Verification: `go vet ./internal/mailconv/ ./bin/mail/` clean;","`go test -race ./internal/mailconv/ ./internal/source/ ./internal/mailsync/","./internal/canon/ ./pkg/contact/` green. Исключения политики (#79): Krylov,","Drafts/Templates/Trash/Junk/Spam/Unsent, *.msf. Mail-send путь не трогали.","Branch `feat/mail-import-gap#79`.","","## 2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)","","Goal: свежий агент по AGENTS.md делает sync-волну, reconcile report,","interaction write — без вопросов в чат. Git ops truth: git = HTTPS+token,","tea CLI для issues/PR (SSH deploy-key :222 на write сломан, #73 closed","obsoleted-by).","","| Step | Status |","|------|--------|","| AGENTS.md: git-ops truth (#81/#73), go-onlyoffice split «примитивы→lib, флоу→2dph», Tools-блок с sync-волной + reconcile + interaction, source map #79 + exclusion policy | done |","| skills: `api-client` — go-onlyoffice split; `etl-handler` — sync-волна + OO-флоу | done |","| `docs/runbook.md` — секция «Sync wave + OO CRM» с verified-командами | done |","| verify: sync `--dry-run` печатает волну; reconcile/import-interaction `--help` и корпус-скан работают; OO-запись — только с env-кредами | done |","","Verification: `go run ./bin/stack/sync.go --dry-run` ok;","`go run -tags=onlyoffice_reconcile_contact ./bin/onlyoffice/reconcile-contact.go --help`","и `go run -tags=onlyoffice_import_interaction ./bin/onlyoffice/import-interaction.go --help`","ok (флаги `--sources/--write/--limit` совпадают с докой). Найдено при","верификации: `--only` в `bin/stack/sync.go` принимает имена шагов","(`mail,mail-import,chats,contact-brain,git-brain,contact-crm`), а не логические","группы — хелп-строка флага это врёт; документировано как есть, баг — на PO","(#81-коммент). Branch `docs/skills-sync#81`.","","## 2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)","","Goal: liblbug 0.19.0 SIGSEGV на вставке в HNSW Leaf_vec (NULL deref в","simsimd_cos_f32, OnDiskHNSWIndex::shrinkForNode) при графе \u003e~1300 нод — наш","канон с ~28k leafs был под крашом (rc=139, тихий рестарт). Порт фикса mdx-1","(medex/brain-stack 6e0f379). Bug A (close/reopen race) у нас уже закрыт #109.","","| Step | Status |","|------|--------|","| `rank/query.go`: VecStmt удалён (комментарий с rationale) | done |","| `search.go`: queryVector → brute-force cosine scan по `l.embedding` (vecScanStmt), cosineToQuery + sqrt64; импорты math/sort | done |","| `write.go`: EnsureIndexes создаёт только FTS `id`; Leaf_vec не создаётся/не требуется, существующий не дропается | done |","| TDD: `vector_test.go` — cosine known-vectors, zero-norm → 0 (не NaN), float64-элементы, truncation | done — `zig go test -tags system_ladybug ./internal/brain/` green (кроме пре-существующих modeldir env-failures), vet clean, gofmt clean |","| reindex live kb.lbug (`bin/brain/index.go --rebuild`) — ждёт PO-подтверждения | pending |","| PLAN.md блок | done |","","Verification: `bin/cgo/zig go test -tags system_ladybug -skip TestModelDir","./internal/brain/` ok; `go test ./internal/brain/rank/` ok. Пре-существующие","падения `TestModelDir*` (HF-модель уже локально в models/, окружение) — не","связаны с этим изменением. Branch `fix/hnsw-cosine-scan#192`.","","## 2026-08-26 — весь mail-корпус в brain (gitea #199)","","Goal: оба корпуса — live `var/corpus/mail` и legacy `var/mail` (#184) — в","brain; шаг волны `mail-index`. Решение B: `LoadMailLeafs` расширен на","несколько root-ов, дедуп по sha256 content-address (одинаковое письмо в","обоих корпусах → один leaf), без копирования данных.","","| Step | Status |","|------|--------|","| `internal/brain/mail_corpus.go`: `MailRoots` + `LoadMailLeafs` multi-root + dedup; вынесен из `corpus.go` (cgo-free) | done |","| `bin/brain/index.go --with-mail` → `brain.MailRoots(root)` | done |","| `bin/stack/sync.go`: шаг `mail-index` (после mail-import, `--with-mail`), brain runner с Zig CGO-тегами `brain_index` | done |","| TDD: `mail_corpus_test.go` (multi-root, dedup, since, limit, attachment) + `sync_test.go` (mail-index план, runner-теги) | done — `go test -race ./internal/brain/... ./bin/stack/... ./internal/mailconv/...` green |","| Полный индекс `index.go --skip --with-mail` в существующую kb.lbug | done — 304 544 leafs, +7 712 новых (прошлый индекс был частичным после corrupt-199); итог info 313 786 |","| Идемпотентность (повтор `--skip --with-mail`) | done — 0 новых |","| Search реальных писем (e2e-outbound, PST-тема) | done — count ≥ 1 |","| docs: AGENTS.md / runbook `--only` список + `--with-mail` | done |","| Известное ограничение: векторный поиск — линейный скан (#192), ~32s при 313k leafs | open — отдельный issue на оптимизацию |","","Branch `feat/mail-corpus-brain#199`, PR #200 → release/v1.","","## 2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)","","Goal: закрыть остаток #79 — два PST-файла (Andriy + копия в бэкапе 128Gb).","`readpst -e` → набор `.eml` → существующий `mailconv.FromEML` (source-тег `pst/`).","Конвертер по решению PO: readpst из пакета `pst-utils` (Ubuntu 24.04; бывший","`libpst-utils`).","","| Step | Status |","|------|--------|","| readpst на хост: системная установка `apt-get install pst-utils` заблокирована (нет passwordless sudo) — установлен локально в `var/dist/readpst` (dpkg-deb -x, `pst.readpst` в config.local.yml) | done (блокер зафиксирован в issue) |","| `internal/source/pst.go`: адаптер `source.PST` (#97-паттерн) — `readpst -e` в вытираемый scratch (`var/tmp/pst/\u003clabel\u003e`), политика #79 через `mailconv.SkipFolder` (немецкие имена: Entwürfe/Vorlagen/Gelöschte Objekte/Junk-E-Mail/Postausgang), контент-адресный копирование в корпус `Out/\u003clabel\u003e/\u003cfolder\u003e/\u003csha256:16\u003e/\u003csha256:16\u003e.eml`; `ImportPST`/`PlanPST` (sync + `mailconv.FromEML`), `--dry-run` план | done |","| нормализация `LibPST-iamunique-\u003cn\u003e` MIME-boundary (readpst вставляет случайный токен на каждом прогоне) — иначе контент-ID нестабильны и повторный прогон плодит дубли | done (найдено на реальных данных, TDD-тест) |","| `bin/mail/import-pst.go`: тонкая CLI-обёртка (flaggy, конфиг pst.*, ctx-first, идемпотентность, state `var/state/pst.json`) | done |","| конфиг: `Config.PST` + секция `pst:` в `etc/brain/config.yml` (sources/readpst/out/state; отсутствие sources = явная ошибка с указанием на конфиг) | done |","| TDD offline (synthetic Alice/Bob/example.com, фейковый readpst через test-seam hook): конверсия .eml→canon (folder=Posteingang), идемпотентный повтор (0 дублей), state-чекпойнт, dry-run, policy-исключения, resolveReadPST (override→PATH→var/dist→ошибка), идентичные архивы (дедуп по seen-set) | done — `go test -race ./internal/source/ ./internal/mailconv/ ./internal/config/` green |","| Прогон реальных PST (оба файла байт-идентичны, sha256 совпадает): 1 уникальное письмо (Outlook-приветствие 2005, `Posteingang`) × 2 лейбла; повторный прогон new=0 skipped=2, дублей нет; readpst извлёк также 78 VCF + 6 ICS (импортируются другими пайплайнами, #68) | done |","| docs: `docs/mail-sources.md` (статусы PST → импортировано, секция PST-импорт), PLAN.md (этот блок) | done |","","Verification: `go build ./...` (кроме pre-existing build-tag пакетов bin/{reasoner,onlyoffice,postgres} — main без тега), `go vet ./...`, `go test -race ./...` зелёные; gofmt чист (shebang-файлы исключены). Корпус: `var/corpus/mail/pst/{pst-andriy,pst-backup-128g-vorlagen}` + state `var/state/pst.json` (1 seen-id). Branch `feat/pst-import#185`.","","## 2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)","","Goal: пилот импорта канала **wheregroup** (3984 письма, канон gator kind=mail)","в kb.lbug как Message/Person узлы + рёбра SENT/TO/CC/BCC/REPLY_TO по write-пути","D-1.2 (#259) и коннектору D-1.1 (#258). Доказать связку «gator канон → граф","2dph» до gmail (D-1.4 #261). Read path не тронут; сырьё var/mail не читается.","","| Item | Status |","|------|--------|","| `pkg/duckdb`: `Exec`/`QueryRows` — произвольный SELECT поверх read_parquet как JSON-совместимые rows (gator-паттерн; LIST(STRUCT)→[]any из map, DECIMAL→float64) | done |","| `internal/mailgraph` (cgo-free): типизированный Row (message_id/folder/from/to/cc/bcc/in_reply_to/subject/date/body/content_hash), `MapRow`/`MapRows` (email lowercase, date RFC3339/TIMESTAMP, NULL-списки), `ReadSQL` (latest на message_id по observed_at + tombstone deleted исключён + channel), `ResolveThreads` (thread_id = корень цепочки in_reply_to, dangling → внешний корень, цикл → fallback), `SortByDate` (родители раньше ответов), `ToInputs` → `brain.MessageInput`, `ComputeStats` (folder-раскладка/REPLY_TO/уникальные email) | done |","| конфиг: `Config.Gator.MailHive` (`gator.mailhive`, шаблон etc/brain/config.yml + config.local.yml, флаг `--hive` / env `GATOR_MAIL_HIVE`) | done |","| `bin/mail/graph.go` (cgo+system_ladybug+mail_graph): `--channel` + dry-run по умолчанию / `--commit`, `--db`, `--skip-existing` (инкремент), live-holder guard (--force), батч-транзакции, отчёт узлы/рёбра после записи | done |","| TDD: юнит cgo-free (MapRow null/регистр/date-форматы, Input/gator_ref `kind=mail#v-\u003chash8\u003e`, SortByDate, ResolveThreads root/dangling/цикл, Stats порядок-независим, ReadSQL latest/tombstone/экранирование) + pkg/duckdb roundtrip parquet | done — зелёные |","| cgo-интеграция (temp Ladybug + synthetic hive parquet): полный цикл → узлы/рёбра, повтор = 0 дублей, `--skip-existing` = 0 новых, dangling REPLY_TO не создаётся | done — `TestImportCycle` (gcc ladybug+duckdb; CI-шаг добавлен, zig duckdb ABI не тянет) |","| live-прогон wheregroup 3984 → `var/kb.lbug` (бэкап `kb.lbug.pre-d13-260.bak`, compose brain остановлен на запись) | done — Message 0→3984, Person 0→167, рёбра SENT 3984 / TO 4855 / CC 302 / BCC 2 / REPLY_TO 639 (после повтора; первый прогон 630 — 9 рёбер досоздались, родитель позже ответа по date) |","| повторный прогон идемпотентен; `--skip-existing` = 0 записанных | done |","| спот-чек: folder INBOX 3143 / Sent 479 / INBOX/Unmatched 362 (сверка с gator), Person andriy.oblivantsev@wheregroup.com, gator_ref `kind=mail#v-\u003chash8\u003e`, REPLY_TO-цепи, thread_id заполнен | done |","","Verification: `go vet ./internal/...` + `go test -race ./internal/mailgraph/ ./internal/config/ ./pkg/duckdb/ ./internal/canon/` green; cgo `go test -tags system_ladybug ./internal/mailgraph/` green (gcc); CLI dry-run/commit/повтор на live. Branch `feat/graph-import-wheregroup#260` off `main`.","","## 2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)","","Goal: детерминированный классификатор адреса сети (L-9.5 #234) —","`person | company | service` — чтобы CRM-экспорт (`--accept-only`) не тащил","сервис-аккаунты/подсистемы/рассылки (GitLab/PayPal/LinkedIn/","markets-platform/chiliproject@trac), а реальные люди и компании-контекст","проходили. База — sender-эвристики `mailconv.IsMachineSender`","(junkDomains/junkLocalParts). Правила: docs/brain/crm-network-filter.md.","","| Item | Status |","|------|--------|","| `internal/mailconv/classify.go` (cgo-free): `ClassifySender(ParsedAddress)` → kind; каскад: релеи людей (linkedin hit-reply/inmail-hit-reply) → person; junk-домены mailconv (общий `isJunkDomain`, одна реализация) + сервис-домены сети (gitlab/paypal/markets-platform/xing/djinni/wellfound/…); local-автоматы (noreply/mailer-daemon/robot/рассылки/security); tool-имена подсистем (chiliproject/projeqtor/trac/wiki/…) в local и левом лейбле домена; `*-request`/`*-owner`/`lists.*` списки; имя-эвристика (Title/ALLCAPS/инициалы/частицы, org/role-слова); fallback first.last@домен; иначе company | done |","| `internal/network`: поле `Link.Kind` в BuildLinks (аддитивно, #234 API не ломает) | done |","| `bin/network`: `--exclude-services` + `--accept-only` исключает kind=service; kind в text/YAML/JSON | done |","| TDD: юнит классификатора (домены/паттерны/границы noreply@люди, robots.txt в имени, релеи) + сети (Kind проставляется, сервисы не в accept-экспорте) | done — зелёные |","| Live-пилот на kb.lbug (read-only): гдеgroup top-60 accept = 44 person / 5 company / 11 service; eslider@ 1240 accept = 317 person / 494 company / 429 service; топ-сервисы eslider@ и chiliproject@trac — service, Astrid Emde/Ruby Rodriguez/Celine Schlindwein — person | done (таблица на #268) |","| Документация: docs/brain/crm-network-filter.md (правила/границы OPEN) + graph-network.md + PLAN.md | done |","","Verification: `go test ./internal/mailconv/ ./internal/network/` + `go vet ./...`","green; cgo `bin/cgo/zig go vet -tags system_ladybug,network_cli ./bin/network/`","green. Branch `feat/network-human-filter#268` off `main`.","","## 2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)","","Goal: write-инструмент — манифест accept-связей (bin/network --accept-only,","network.Manifest) → контакт Person в OnlyOffice CRM (email/имя/тег источника","`2dph:network:\u003ctarget\u003e`/about со сводкой + premises), идемпотентно по email;","сервис-аккаунты (N-1.1 #268) пропущены; dry-run/report-only ничего не пишет.","","| Item | Status |","|------|--------|","| `internal/network/manifest.go`: тип `network.Manifest` (crmDoc/crmLink из bin/network, L-9.5 #234) — общий контракт продюсера и коннектора; bin/network печатает его | done |","| `internal/ooimport` (cgo-free): `ParseManifest` → `BuildPlan` (маппинг person/company→Person: имя через mailconv.SplitPersonName, fallback локальная часть email; kind=service/без email → skipped; тег default `2dph:network:\u003ctarget-email\u003e`; about: `{msgs} писем / {threads} тредов / {replies} ответов, период {period}; premises: mail/commit … (+N ещё); source: \u003cmanifest\u003e`) → `Run` (BuildContactEmailIndex один проход → matched skip / create: CreatePerson + AddContactInfo email primary + CreateContactTag/AddContactTag; rollback созданного при сбое email/тега; dry-run/limit) | done |","| CLI `bin/onlyoffice/import-network.go` (shebang, tag onlyoffice_import_network): `--manifest \u003cfile|-\u003e` (обязателен), `--dry-run`/`--write` (mutually exclusive), `--limit N`, `--tag`; report only по умолчанию; отчёт created/matched/skipped/failed/pending | done |","| Маппинг-решения: linkedin-релеи людей (hit-reply@linkedin.com, display name реальный) — Person как есть (email=релей, имя=человек); роль-ящики/компании (info@/alle@, kind=company) — Person с fallback-именем (компания-группировка — N-1.4 #271); kind=service не идёт | done (док: docs/brain/oo-network-import.md) |","| TDD офлайн: ParseManifest (+roundtrip продюсер-консьюмер yaml), BuildPlan (имя/email lowercase/тег/about-точная строка/sort/skip), splitByExisting (повтор = 0 новых) | done — зелёные |","| Интеграция с живым OO (`//go:build integration`, creds ONLYOFFICE_URL/USER/PASS, без creds — skip): dry-run ничего не пишет → write создаёт контакт email/тег/about → повтор = 0 новых/all matched; --limit создаёт часть, добивание — остальное; тестовый контакт удаляется в t.Cleanup (тег остаётся 0-count — API не удаляет теги) | done — зелёные на office.produktor.io |","| Документация: docs/brain/oo-network-import.md (маппинг/команды/границы) + graph-network.md §4 + PLAN.md | done |","","Verification: `go test ./internal/network/ ./internal/mailconv/ ./internal/ooimport/`","+ `go vet ./...` green; integration `go test -tags=integration ./internal/ooimport/`","green (live OO); CLI dry-run на fixture — report-only, ничего не пишет.","Branch `feat/oo-crm-connector#269` off `main`.","","## 2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)","","Goal: по итогам пилота N-1.3 (#270) довести гдеgroup-импорт до структуры","«деловой контакт»: (1) role-ящики/компании гдеgroup сгруппировать под","компанией по домену email; (2) существующим matched-контактам (32 из #270,","в т.ч. топ-5 из #85) **аддитивно** дописать тег `2dph:network:\u003ctarget\u003e` +","about-premises-сводку (решение владельца #267); (3) дедуп — только report","(реальных дублей пилот не выявил, мульти-адреса — YAGNI эпика).","","| Item | Status |","|------|--------|","| `internal/ooimport`: поле `Contact.Kind` (класс связи манифеста person/company; service отфильтрован) — основа для группировки | done |","| `company.go`: `CompanyRule` (домен→компания), `ParseCompanies` (YAML, валидация: домен с точкой, не email, без повторов; сорт), `EmailDomain`, `GroupCompanyLinks` (только kind=company → компания по домену; person-kind не трогаем; company без правила — unmapped; два домена → одна компания схлопываются) | done |","| `RunCompanies` (write/dry-run): FindCompany → если нет CreateCompany → GetContact + UpdatePerson с companyId; повтор = 0 изменений (found/already); роль-ящик не в CRM — notfound | done |","| `backfill.go`: `BackfillChanges` (тег если нет; about-сводка только если about пуст/пробельный — ручные правки не перезаписываем), `RunBackfill` (hasTag по ListContactsByTag, about/имена GetContact; dry-run Would; CreateContactTag идемпотентно; повтор = 0 Updated) | done |","| CLI `import-network.go`: флаги `--backfill`, `--companies \u003cyaml|-\u003e` (после Run; оба идемпотентны; отчёты updated/would/unchanged + found/created/linked/already/unmapped) | done |","| Косметика: `mailconv.SplitPersonName` снимает трейлинговую « (Org)»-декорацию («Astrid Emde (FOSSGIS e.V.)» → Astrid/Emde; пилот #270: given «Astrid Emde (FOSSGIS»/family «e.V.)») | done |","| TDD офлайн: mailconv SplitPersonName (декорация/без/вложенная/вся-в-скобках → fallback), ParseCompanies (сорт/трим/ошибки), GroupCompanyLinks (role-ящики/сорт/unmapped/person-kind не трогаем), BackfillChanges (аддитивность: ручной about цел, пробельный about — сводка) | done — зелёные |","| Интеграция с живым OO (`//go:build integration`, без creds — skip): role-ящик → компания создана+слинкован → повтор already=1; matched: тег+about дописаны, ручной about B не перезаписан, повтор = 0 Updated | done (office.produktor.io) |","| Документация: docs/brain/oo-network-import.md (N-1.4: --backfill/--companies, границы: person-kind не линкуем, Name маппинга = display name компании CRM) + PLAN.md | done |","","Verification: `go test ./internal/mailconv/ ./internal/ooimport/ ./internal/network/`","+ `go vet ./...` green; integration `go test -tags=integration ./internal/ooimport/`","green (live OO); CLI `go vet -tags=onlyoffice_import_network ./bin/onlyoffice/`.","Дедуп: реальных дублей Person/компаний пилот не выявил — merge не","применялся (только report; правило #271 «строго по явному списку»).","Branch `feat/oo-crm-company#271` off `main`.","","## 2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)","","Goal: единый клиентский слой чтения brain — typed-клиент поверх read-контракта","(P-9.4 #240) вместо того, что каждый потребитель зовёт bin/brain/HTTP/MCP","по-своему; плюс «гейт facts» (клиент не выдаёт not-confirmed как подтверждённое).","Клиент читает только через контракт (HTTP :8630), kb.lbug напрямую не открывает.","","| Item | Status |","|------|--------|","| `pkg/brainclient` (cgo-free): `Client` (Config base/token/timeout, транспорт) + typed `Search`/`Get`/`Stats`/`Audit` → `internal/contract`-ответы; ответы валидируются контрактными валидаторами (формат + contract_version; несовместимый сервис — ошибка) | done |","| Гейт facts: `Gate(root, confidence)` (confirmed только facts+confirmed/legacy-пустое), `Client.Facts` (search --root facts → confirmed[] / not_confirmed[] с reason), `GateAudit` (гистограмма facts-корня; not_confirmed\u003e0 = нарушение). 2v2-противоречия (hypothesis, D16) в confirmed не попадают | done |","| CLI `bin/brain/client.go` (shebang, brain_client, cgo-free): search/get/stats/audit, --json/--root facts\\|info/--as-of/-n/--no-web/--base/--token; реализация в `pkg/brainclient/cli` (Run, покрыта тестами); audit при not_confirmed\u003e0 → exit 1 | done |","| TDD: gate-таблицы (facts+confirmed/legacy vs hypothesis/partial/info), GateSearch/GateAudit на фикстурах; httptest-клиент (typed-декод, валидация, ошибки HTTP, auth-header, root=facts параметр); CLI-проводка (флаги, JSON-формы, exit-коды) | done — `go test ./pkg/brainclient/...` зелёные |","| Live на :8630 (db 105220 leafs): stats/audit (facts=21 confirmed, not_confirmed=0, exit 0), search --root facts (onlyoffice postgres → 1 confirmed), get --body (source «docker ps x compose:...»), info-хиты в default-режиме помечены (not confirmed) | done |","| Документация: docs/brain/read-contract.md §«Клиентский слой (P-9.5)» (SDK/CLI, гейт, примеры search-факт + audit-карточка, кросс-репо путь через P-9.6) + skills/brain/SKILL.md §«Read через контракт» + PLAN.md | done |","","Кросс-репо (gator/cv): вынос read-контракта и клиента в публичный go-* модуль —","P-9.6 (ADR, эпик #239); до него внешние потребители читают по форматам","read-contract.md через HTTP/MCP/CLI.","","Verification: `go vet ./...` + `go test ./pkg/brainclient/...` green","(CLI-логика в pkg/brainclient/cli входит в обычный ./...; bin/brain — shebang-","mains за эксклюзивными тегами, по D14 компилируются пофайлово) —","bin/brain/client.go собран и прогнан `go run -tags=brain_client`;","`./bin/facts/audit.go self` ok. Branch `feat/brain-client#241` off `main`."],"stylingDirectives":[[[0,30,"pl-mh"],[2,30,"pl-en"]],[],[],[],[],[],[[8,10,"pl-s"],[15,17,"pl-s"],[24,25,"pl-s"],[25,28,"pl-s"],[26,28,"pl-corl"],[28,29,"pl-s"],[29,30,"pl-s"],[30,77,"pl-corl"],[77,78,"pl-s"]],[[20,21,"pl-s"],[23,24,"pl-s"],[24,25,"pl-s"],[25,75,"pl-corl"],[75,76,"pl-s"]],[[4,5,"pl-s"],[5,7,"pl-s"],[6,7,"pl-corl"],[7,8,"pl-s"],[8,9,"pl-s"],[9,55,"pl-corl"],[55,56,"pl-s"]],[[0,1,"pl-s"],[1,4,"pl-s"],[2,4,"pl-corl"],[4,5,"pl-s"],[5,6,"pl-s"],[6,53,"pl-corl"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,4,"pl-s"],[2,4,"pl-corl"],[4,5,"pl-s"],[5,6,"pl-s"],[6,53,"pl-corl"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,4,"pl-s"],[2,4,"pl-corl"],[4,5,"pl-s"],[5,6,"pl-s"],[6,53,"pl-corl"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,4,"pl-s"],[2,4,"pl-corl"],[4,5,"pl-s"],[5,6,"pl-s"],[6,53,"pl-corl"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,5,"pl-s"],[2,5,"pl-corl"],[5,6,"pl-s"],[6,7,"pl-s"],[7,55,"pl-corl"],[55,56,"pl-s"]],[[0,1,"pl-s"],[1,5,"pl-s"],[2,5,"pl-corl"],[5,6,"pl-s"],[6,7,"pl-s"],[7,55,"pl-corl"],[55,56,"pl-s"]],[[1,2,"pl-s"],[2,26,"pl-c1"],[26,27,"pl-s"]],[[0,1,"pl-s"],[1,35,"pl-c1"],[35,36,"pl-s"]],[[0,1,"pl-s"],[1,4,"pl-s"],[2,4,"pl-corl"],[4,5,"pl-s"],[5,6,"pl-s"],[6,53,"pl-corl"],[53,54,"pl-s"]],[[32,33,"pl-s"],[33,60,"pl-c1"],[60,61,"pl-s"]],[[0,1,"pl-s"],[22,23,"pl-s"],[23,24,"pl-s"],[24,45,"pl-corl"],[45,46,"pl-s"]],[[5,6,"pl-s"],[21,22,"pl-s"],[22,23,"pl-s"],[23,38,"pl-corl"],[38,39,"pl-s"]],[],[[0,7,"pl-mh"],[3,7,"pl-en"]],[],[[40,41,"pl-s"],[41,46,"pl-c1"],[46,47,"pl-s"]],[],[[0,1,"pl-v"],[4,6,"pl-s"],[20,22,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[8,10,"pl-s"],[22,24,"pl-s"],[35,37,"pl-s"]],[[0,1,"pl-v"],[33,34,"pl-s"],[34,38,"pl-c1"],[38,39,"pl-s"],[41,42,"pl-s"],[42,47,"pl-c1"],[47,48,"pl-s"],[51,52,"pl-s"],[52,56,"pl-c1"],[56,57,"pl-s"],[60,61,"pl-s"],[61,71,"pl-c1"],[71,72,"pl-s"]],[[3,4,"pl-s"],[4,13,"pl-c1"],[13,14,"pl-s"],[17,18,"pl-s"],[18,25,"pl-c1"],[25,26,"pl-s"],[29,30,"pl-s"],[30,40,"pl-c1"],[40,41,"pl-s"],[44,45,"pl-s"],[45,55,"pl-c1"],[55,56,"pl-s"],[58,59,"pl-s"],[59,62,"pl-c1"],[62,63,"pl-s"],[65,66,"pl-s"],[66,71,"pl-c1"],[71,72,"pl-s"],[74,75,"pl-s"],[75,79,"pl-c1"],[79,80,"pl-s"]],[],[[9,11,"pl-s"],[20,22,"pl-s"],[60,61,"pl-s"],[61,71,"pl-c1"],[71,72,"pl-s"]],[],[[18,20,"pl-s"]],[[0,1,"pl-s"],[1,16,"pl-c1"],[16,17,"pl-s"],[18,20,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"]],[],[[0,1,"pl-ml"],[4,5,"pl-ml"],[15,16,"pl-ml"],[24,25,"pl-ml"]],[[0,1,"pl-ml"],[4,5,"pl-ml"],[15,16,"pl-ml"],[24,25,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[18,19,"pl-ml"],[107,108,"pl-s"],[108,121,"pl-c1"],[121,122,"pl-s"],[145,146,"pl-s"],[146,156,"pl-c1"],[156,157,"pl-s"],[179,180,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[21,22,"pl-ml"],[40,42,"pl-s"],[57,59,"pl-s"],[60,61,"pl-s"],[61,68,"pl-c1"],[68,69,"pl-s"],[108,109,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[18,19,"pl-ml"],[30,31,"pl-s"],[31,48,"pl-c1"],[48,49,"pl-s"],[51,52,"pl-s"],[52,70,"pl-c1"],[70,71,"pl-s"],[97,98,"pl-s"],[98,114,"pl-c1"],[114,115,"pl-s"],[143,144,"pl-s"],[144,151,"pl-c1"],[151,152,"pl-s"],[238,239,"pl-s"],[239,248,"pl-c1"],[248,249,"pl-s"],[270,271,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[18,19,"pl-ml"],[20,22,"pl-s"],[31,33,"pl-s"],[34,35,"pl-s"],[35,69,"pl-c1"],[69,70,"pl-s"],[98,99,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[14,15,"pl-ml"],[16,18,"pl-s"],[25,27,"pl-s"],[121,122,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[20,21,"pl-ml"],[22,24,"pl-s"],[33,35,"pl-s"],[56,57,"pl-s"],[57,76,"pl-c1"],[76,77,"pl-s"],[79,80,"pl-s"],[80,98,"pl-c1"],[98,99,"pl-s"],[112,113,"pl-s"],[113,127,"pl-c1"],[127,128,"pl-s"],[171,172,"pl-s"],[172,195,"pl-c1"],[195,196,"pl-s"],[275,276,"pl-s"],[276,286,"pl-c1"],[286,287,"pl-s"],[289,290,"pl-s"],[290,306,"pl-c1"],[306,307,"pl-s"],[329,330,"pl-s"],[330,353,"pl-c1"],[353,354,"pl-s"],[388,389,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[17,18,"pl-ml"],[19,20,"pl-s"],[20,27,"pl-c1"],[27,28,"pl-s"],[29,30,"pl-s"],[30,37,"pl-c1"],[37,38,"pl-s"],[103,104,"pl-s"],[104,118,"pl-c1"],[118,119,"pl-s"],[122,123,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[16,17,"pl-ml"],[62,63,"pl-s"],[63,78,"pl-c1"],[78,79,"pl-s"],[141,142,"pl-ml"]],[[0,1,"pl-ml"],[5,6,"pl-ml"],[24,25,"pl-ml"],[97,98,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[19,20,"pl-ml"],[47,48,"pl-s"],[48,81,"pl-c1"],[81,82,"pl-s"],[84,85,"pl-s"],[85,132,"pl-c1"],[132,133,"pl-s"],[143,144,"pl-s"],[144,154,"pl-c1"],[154,155,"pl-s"],[168,169,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[20,21,"pl-ml"],[22,23,"pl-s"],[23,27,"pl-c1"],[27,28,"pl-s"],[37,38,"pl-s"],[38,43,"pl-c1"],[43,44,"pl-s"],[57,58,"pl-s"],[58,62,"pl-c1"],[62,63,"pl-s"],[82,83,"pl-s"],[83,92,"pl-c1"],[92,93,"pl-s"],[116,117,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[22,23,"pl-ml"],[57,59,"pl-s"],[105,107,"pl-s"],[124,125,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[18,19,"pl-ml"],[32,33,"pl-s"],[33,69,"pl-c1"],[69,70,"pl-s"],[120,121,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[22,23,"pl-ml"],[24,25,"pl-s"],[25,50,"pl-c1"],[50,51,"pl-s"],[66,67,"pl-s"],[67,86,"pl-c1"],[86,87,"pl-s"],[105,106,"pl-s"],[106,115,"pl-c1"],[115,116,"pl-s"],[127,128,"pl-s"],[128,134,"pl-c1"],[134,135,"pl-s"],[138,139,"pl-s"],[139,146,"pl-c1"],[146,147,"pl-s"],[152,153,"pl-s"],[153,166,"pl-c1"],[166,167,"pl-s"],[188,189,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[13,14,"pl-ml"],[21,22,"pl-s"],[22,23,"pl-s"],[23,35,"pl-c1"],[35,36,"pl-s"],[36,37,"pl-s"],[37,38,"pl-s"],[38,75,"pl-corl"],[75,76,"pl-s"],[89,90,"pl-s"],[96,97,"pl-s"],[97,98,"pl-s"],[98,142,"pl-corl"],[142,143,"pl-s"],[152,153,"pl-s"],[153,165,"pl-c1"],[165,166,"pl-s"],[217,218,"pl-s"],[218,222,"pl-c1"],[222,223,"pl-s"],[261,262,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[23,24,"pl-ml"],[56,57,"pl-s"],[57,72,"pl-c1"],[72,73,"pl-s"],[101,103,"pl-s"],[121,123,"pl-s"],[159,161,"pl-s"],[178,180,"pl-s"],[230,231,"pl-s"],[231,245,"pl-c1"],[245,246,"pl-s"],[268,269,"pl-s"],[269,295,"pl-c1"],[295,296,"pl-s"],[298,299,"pl-s"],[299,302,"pl-s"],[300,302,"pl-corl"],[302,303,"pl-s"],[303,304,"pl-s"],[304,351,"pl-corl"],[351,352,"pl-s"],[354,355,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[23,24,"pl-ml"],[42,43,"pl-s"],[48,49,"pl-s"],[95,96,"pl-s"],[96,115,"pl-c1"],[115,116,"pl-s"],[124,125,"pl-s"],[125,128,"pl-c1"],[128,129,"pl-s"],[164,165,"pl-s"],[165,174,"pl-c1"],[174,175,"pl-s"],[176,177,"pl-s"],[177,184,"pl-c1"],[184,185,"pl-s"],[186,187,"pl-s"],[187,194,"pl-c1"],[194,195,"pl-s"],[208,209,"pl-s"],[209,215,"pl-c1"],[215,216,"pl-s"],[221,222,"pl-s"],[222,230,"pl-c1"],[230,231,"pl-s"],[278,279,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[17,18,"pl-ml"],[52,53,"pl-s"],[53,70,"pl-c1"],[70,71,"pl-s"],[79,80,"pl-s"],[80,95,"pl-c1"],[95,96,"pl-s"],[107,108,"pl-s"],[108,132,"pl-c1"],[132,133,"pl-s"],[137,138,"pl-s"],[138,154,"pl-c1"],[154,155,"pl-s"],[195,196,"pl-s"],[196,217,"pl-c1"],[217,218,"pl-s"],[237,238,"pl-s"],[238,246,"pl-c1"],[246,247,"pl-s"],[249,250,"pl-s"],[250,266,"pl-c1"],[266,267,"pl-s"],[269,270,"pl-s"],[270,276,"pl-c1"],[276,277,"pl-s"],[308,309,"pl-s"],[309,317,"pl-c1"],[317,318,"pl-s"],[330,331,"pl-s"],[331,337,"pl-c1"],[337,338,"pl-s"],[339,340,"pl-s"],[340,343,"pl-c1"],[343,344,"pl-s"],[345,346,"pl-s"],[346,351,"pl-c1"],[351,352,"pl-s"],[416,417,"pl-s"],[417,420,"pl-s"],[418,420,"pl-corl"],[420,421,"pl-s"],[421,422,"pl-s"],[422,469,"pl-corl"],[469,470,"pl-s"],[472,473,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[20,21,"pl-ml"],[22,23,"pl-s"],[29,30,"pl-s"],[30,31,"pl-s"],[31,63,"pl-corl"],[63,64,"pl-s"],[69,70,"pl-s"],[70,93,"pl-c1"],[93,94,"pl-s"],[100,101,"pl-s"],[101,116,"pl-c1"],[116,117,"pl-s"],[217,218,"pl-s"],[218,227,"pl-c1"],[227,228,"pl-s"],[242,243,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[18,19,"pl-ml"],[62,63,"pl-s"],[63,78,"pl-c1"],[78,79,"pl-s"],[89,90,"pl-s"],[90,108,"pl-c1"],[108,109,"pl-s"],[120,121,"pl-s"],[121,138,"pl-c1"],[138,139,"pl-s"],[141,142,"pl-s"],[142,151,"pl-c1"],[151,152,"pl-s"],[221,222,"pl-s"],[222,228,"pl-c1"],[228,229,"pl-s"],[230,231,"pl-s"],[231,234,"pl-c1"],[234,235,"pl-s"],[236,237,"pl-s"],[237,242,"pl-c1"],[242,243,"pl-s"],[244,245,"pl-s"],[245,250,"pl-c1"],[250,251,"pl-s"],[252,253,"pl-s"],[253,259,"pl-c1"],[259,260,"pl-s"],[263,264,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[12,13,"pl-ml"],[54,56,"pl-s"],[59,61,"pl-s"],[63,64,"pl-s"],[64,75,"pl-c1"],[75,76,"pl-s"],[79,80,"pl-s"],[80,106,"pl-c1"],[106,107,"pl-s"],[119,120,"pl-s"],[120,131,"pl-c1"],[131,132,"pl-s"],[198,199,"pl-s"],[199,210,"pl-c1"],[210,211,"pl-s"],[253,254,"pl-s"],[254,259,"pl-c1"],[259,260,"pl-s"],[262,263,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[18,19,"pl-ml"],[20,22,"pl-s"],[31,33,"pl-s"],[46,47,"pl-s"],[47,57,"pl-c1"],[57,58,"pl-s"],[60,61,"pl-s"],[61,79,"pl-c1"],[79,80,"pl-s"],[114,116,"pl-s"],[123,125,"pl-s"],[233,235,"pl-s"],[247,249,"pl-s"],[267,268,"pl-s"],[268,271,"pl-s"],[269,271,"pl-corl"],[271,272,"pl-s"],[272,273,"pl-s"],[273,320,"pl-corl"],[320,321,"pl-s"],[323,324,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[12,13,"pl-ml"],[14,16,"pl-s"],[22,24,"pl-s"],[26,27,"pl-s"],[27,53,"pl-c1"],[53,54,"pl-s"],[96,97,"pl-s"],[97,104,"pl-c1"],[104,105,"pl-s"],[122,123,"pl-s"],[123,161,"pl-c1"],[161,162,"pl-s"],[184,185,"pl-s"],[185,189,"pl-c1"],[189,190,"pl-s"],[239,240,"pl-s"],[240,250,"pl-c1"],[250,251,"pl-s"],[253,254,"pl-s"],[254,257,"pl-s"],[255,257,"pl-corl"],[257,258,"pl-s"],[258,259,"pl-s"],[259,306,"pl-corl"],[306,307,"pl-s"],[309,310,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[23,24,"pl-ml"],[30,31,"pl-s"],[31,41,"pl-c1"],[41,42,"pl-s"],[45,46,"pl-s"],[46,54,"pl-c1"],[54,55,"pl-s"],[109,110,"pl-s"],[110,117,"pl-c1"],[117,118,"pl-s"],[125,126,"pl-s"],[126,131,"pl-c1"],[131,132,"pl-s"],[170,171,"pl-s"],[171,189,"pl-c1"],[189,190,"pl-s"],[248,249,"pl-s"],[249,252,"pl-s"],[250,252,"pl-corl"],[252,253,"pl-s"],[253,254,"pl-s"],[254,301,"pl-corl"],[301,302,"pl-s"],[304,305,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[25,26,"pl-ml"],[50,51,"pl-s"],[51,55,"pl-c1"],[55,56,"pl-s"],[91,92,"pl-s"],[92,107,"pl-c1"],[107,108,"pl-s"],[121,122,"pl-s"],[122,139,"pl-c1"],[139,140,"pl-s"],[157,158,"pl-s"],[158,164,"pl-c1"],[164,165,"pl-s"],[220,221,"pl-s"],[221,242,"pl-c1"],[242,243,"pl-s"],[245,246,"pl-s"],[246,251,"pl-c1"],[251,252,"pl-s"],[258,259,"pl-s"],[259,277,"pl-c1"],[277,278,"pl-s"],[282,283,"pl-s"],[283,297,"pl-c1"],[297,298,"pl-s"],[357,358,"pl-s"],[358,366,"pl-c1"],[366,367,"pl-s"],[368,369,"pl-s"],[369,375,"pl-c1"],[375,376,"pl-s"],[405,406,"pl-s"],[406,410,"pl-c1"],[410,411,"pl-s"],[426,427,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[20,24,"pl-s"],[21,24,"pl-corl"],[25,29,"pl-s"],[26,29,"pl-corl"],[31,32,"pl-ml"],[60,61,"pl-s"],[61,79,"pl-c1"],[79,80,"pl-s"],[95,96,"pl-s"],[96,135,"pl-c1"],[135,136,"pl-s"],[138,139,"pl-s"],[228,231,"pl-s"],[229,231,"pl-corl"],[391,392,"pl-s"],[449,453,"pl-s"],[450,453,"pl-corl"],[455,456,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[25,29,"pl-s"],[26,29,"pl-corl"],[30,34,"pl-s"],[31,34,"pl-corl"],[36,37,"pl-ml"],[73,74,"pl-s"],[74,92,"pl-c1"],[92,93,"pl-s"],[95,97,"pl-s"],[100,102,"pl-s"],[159,160,"pl-s"],[160,170,"pl-c1"],[170,171,"pl-s"],[249,250,"pl-s"],[250,270,"pl-c1"],[270,271,"pl-s"],[291,292,"pl-s"],[292,298,"pl-c1"],[298,299,"pl-s"],[336,337,"pl-s"],[337,353,"pl-c1"],[353,354,"pl-s"],[404,405,"pl-s"],[405,414,"pl-c1"],[414,415,"pl-s"],[417,421,"pl-s"],[418,421,"pl-corl"],[424,425,"pl-s"],[425,436,"pl-c1"],[436,437,"pl-s"],[564,565,"pl-s"],[565,575,"pl-c1"],[575,576,"pl-s"],[652,656,"pl-s"],[653,656,"pl-corl"],[677,678,"pl-s"],[697,698,"pl-s"],[738,742,"pl-s"],[739,742,"pl-corl"],[743,747,"pl-s"],[744,747,"pl-corl"],[750,751,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[20,21,"pl-ml"],[22,23,"pl-s"],[23,36,"pl-c1"],[36,37,"pl-s"],[52,53,"pl-s"],[53,71,"pl-c1"],[71,72,"pl-s"],[103,104,"pl-s"],[104,112,"pl-c1"],[112,113,"pl-s"],[125,126,"pl-s"],[126,196,"pl-c1"],[196,197,"pl-s"],[207,208,"pl-s"],[208,221,"pl-c1"],[221,222,"pl-s"],[233,234,"pl-s"],[234,254,"pl-c1"],[254,255,"pl-s"],[311,312,"pl-s"],[312,315,"pl-s"],[313,315,"pl-corl"],[315,316,"pl-s"],[316,317,"pl-s"],[317,364,"pl-corl"],[364,365,"pl-s"],[366,367,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[22,23,"pl-ml"],[74,75,"pl-s"],[75,103,"pl-c1"],[103,104,"pl-s"],[106,107,"pl-s"],[107,122,"pl-c1"],[122,123,"pl-s"],[132,133,"pl-s"],[133,153,"pl-c1"],[153,154,"pl-s"],[157,158,"pl-s"],[158,174,"pl-c1"],[174,175,"pl-s"],[178,179,"pl-s"],[179,183,"pl-c1"],[183,184,"pl-s"],[329,330,"pl-s"],[330,339,"pl-c1"],[339,340,"pl-s"],[349,350,"pl-s"],[350,354,"pl-c1"],[354,355,"pl-s"],[358,359,"pl-s"],[359,373,"pl-c1"],[373,374,"pl-s"],[409,410,"pl-s"],[410,413,"pl-s"],[411,413,"pl-corl"],[413,414,"pl-s"],[414,415,"pl-s"],[415,462,"pl-corl"],[462,463,"pl-s"],[465,466,"pl-s"],[466,469,"pl-s"],[467,469,"pl-corl"],[469,470,"pl-s"],[470,471,"pl-s"],[471,518,"pl-corl"],[518,519,"pl-s"],[520,521,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[32,33,"pl-ml"],[47,48,"pl-s"],[48,54,"pl-c1"],[54,55,"pl-s"],[57,58,"pl-s"],[58,72,"pl-c1"],[72,73,"pl-s"],[167,168,"pl-s"],[168,188,"pl-c1"],[188,189,"pl-s"],[204,205,"pl-s"],[205,208,"pl-s"],[206,208,"pl-corl"],[208,209,"pl-s"],[209,210,"pl-s"],[210,257,"pl-corl"],[257,258,"pl-s"],[259,260,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[19,20,"pl-ml"],[45,46,"pl-s"],[46,60,"pl-c1"],[60,61,"pl-s"],[139,140,"pl-s"],[140,154,"pl-c1"],[154,155,"pl-s"],[176,177,"pl-s"],[177,180,"pl-s"],[178,180,"pl-corl"],[180,181,"pl-s"],[181,182,"pl-s"],[182,229,"pl-corl"],[229,230,"pl-s"],[232,233,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[20,21,"pl-ml"],[26,27,"pl-s"],[27,50,"pl-c1"],[50,51,"pl-s"],[69,70,"pl-s"],[70,96,"pl-c1"],[96,97,"pl-s"],[183,184,"pl-s"],[184,207,"pl-c1"],[207,208,"pl-s"],[266,267,"pl-s"],[267,274,"pl-c1"],[274,275,"pl-s"],[279,280,"pl-s"],[280,286,"pl-c1"],[286,287,"pl-s"],[289,290,"pl-s"],[290,306,"pl-c1"],[306,307,"pl-s"],[309,310,"pl-s"],[310,331,"pl-c1"],[331,332,"pl-s"],[335,336,"pl-s"],[336,362,"pl-c1"],[362,363,"pl-s"],[404,405,"pl-s"],[405,428,"pl-c1"],[428,429,"pl-s"],[442,443,"pl-s"],[443,447,"pl-s"],[444,447,"pl-corl"],[447,448,"pl-s"],[448,449,"pl-s"],[449,497,"pl-corl"],[497,498,"pl-s"],[499,500,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[20,21,"pl-ml"],[209,210,"pl-s"],[210,223,"pl-c1"],[223,224,"pl-s"],[232,233,"pl-s"],[233,236,"pl-s"],[234,236,"pl-corl"],[236,237,"pl-s"],[237,238,"pl-s"],[238,285,"pl-corl"],[285,286,"pl-s"],[287,288,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[29,30,"pl-ml"],[61,62,"pl-s"],[62,69,"pl-c1"],[69,70,"pl-s"],[73,74,"pl-s"],[74,83,"pl-c1"],[83,84,"pl-s"],[97,98,"pl-s"],[98,113,"pl-c1"],[113,114,"pl-s"],[120,121,"pl-s"],[121,122,"pl-c1"],[122,123,"pl-s"],[133,134,"pl-s"],[134,148,"pl-c1"],[148,149,"pl-s"],[180,181,"pl-s"],[181,190,"pl-c1"],[190,191,"pl-s"],[248,249,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[34,35,"pl-ml"],[45,46,"pl-s"],[46,108,"pl-c1"],[108,109,"pl-s"],[159,160,"pl-s"],[160,166,"pl-c1"],[166,167,"pl-s"],[173,174,"pl-s"],[174,185,"pl-c1"],[185,186,"pl-s"],[193,194,"pl-s"],[194,204,"pl-c1"],[204,205,"pl-s"],[350,351,"pl-s"],[351,363,"pl-c1"],[363,364,"pl-s"],[380,381,"pl-s"],[381,391,"pl-c1"],[391,392,"pl-s"],[409,410,"pl-s"],[410,441,"pl-c1"],[441,442,"pl-s"],[460,461,"pl-s"],[461,492,"pl-c1"],[492,493,"pl-s"],[499,500,"pl-s"],[500,516,"pl-c1"],[516,517,"pl-s"],[533,534,"pl-s"],[534,537,"pl-s"],[535,537,"pl-corl"],[537,538,"pl-s"],[538,539,"pl-s"],[539,586,"pl-corl"],[586,587,"pl-s"],[589,590,"pl-s"],[590,593,"pl-s"],[591,593,"pl-corl"],[593,594,"pl-s"],[594,595,"pl-s"],[595,642,"pl-corl"],[642,643,"pl-s"],[644,645,"pl-s"],[645,649,"pl-s"],[646,649,"pl-corl"],[649,650,"pl-s"],[650,651,"pl-s"],[651,699,"pl-corl"],[699,700,"pl-s"],[702,703,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[22,23,"pl-ml"],[72,73,"pl-s"],[73,114,"pl-c1"],[114,115,"pl-s"],[199,200,"pl-s"],[200,230,"pl-c1"],[230,231,"pl-s"],[283,284,"pl-s"],[284,300,"pl-c1"],[300,301,"pl-s"],[312,313,"pl-s"],[313,340,"pl-c1"],[340,341,"pl-s"],[389,390,"pl-s"],[390,397,"pl-c1"],[397,398,"pl-s"],[400,401,"pl-s"],[401,404,"pl-s"],[402,404,"pl-corl"],[404,405,"pl-s"],[405,406,"pl-s"],[406,453,"pl-corl"],[453,454,"pl-s"],[455,456,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-ml"],[21,25,"pl-s"],[22,25,"pl-corl"],[27,28,"pl-ml"],[45,49,"pl-s"],[46,49,"pl-corl"],[73,74,"pl-s"],[74,98,"pl-c1"],[98,99,"pl-s"],[115,117,"pl-s"],[128,130,"pl-s"],[307,308,"pl-s"],[308,317,"pl-c1"],[317,318,"pl-s"],[321,322,"pl-s"],[322,335,"pl-c1"],[335,336,"pl-s"],[493,494,"pl-s"],[494,499,"pl-c1"],[499,500,"pl-s"],[627,631,"pl-s"],[628,631,"pl-corl"],[708,709,"pl-ml"]],[],[],[[0,15,"pl-mh"],[3,15,"pl-en"]],[],[[0,3,"pl-s"],[3,3,"pl-c1"]],[[0,5,"pl-c1"]],[[0,21,"pl-c1"]],[[0,76,"pl-c1"]],[[0,100,"pl-c1"]],[[0,6,"pl-c1"]],[[0,57,"pl-c1"]],[[0,66,"pl-c1"]],[[0,63,"pl-c1"]],[[0,82,"pl-c1"]],[[0,73,"pl-c1"]],[[0,79,"pl-c1"]],[[0,88,"pl-c1"]],[[0,97,"pl-c1"]],[[0,79,"pl-c1"]],[[0,49,"pl-c1"]],[[0,64,"pl-c1"]],[[0,75,"pl-c1"]],[[0,46,"pl-c1"]],[[0,69,"pl-c1"]],[[0,68,"pl-c1"]],[[0,81,"pl-c1"]],[[0,79,"pl-c1"]],[[0,63,"pl-c1"]],[[0,59,"pl-c1"]],[[0,59,"pl-c1"]],[[0,38,"pl-c1"]],[[0,53,"pl-c1"]],[[0,7,"pl-c1"]],[[0,67,"pl-c1"]],[[0,53,"pl-c1"]],[[0,56,"pl-c1"]],[[0,48,"pl-c1"]],[[0,6,"pl-c1"]],[[0,69,"pl-c1"]],[[0,62,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,22,"pl-mh"],[3,22,"pl-en"]],[],[[13,14,"pl-s"],[14,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-s"],[1,25,"pl-c1"],[25,26,"pl-s"],[36,37,"pl-s"],[37,41,"pl-c1"],[41,42,"pl-s"]],[[9,10,"pl-s"],[10,28,"pl-c1"],[28,29,"pl-s"],[31,35,"pl-s"],[32,35,"pl-corl"]],[[16,20,"pl-s"],[17,20,"pl-corl"]],[[7,8,"pl-s"],[8,92,"pl-c1"],[92,93,"pl-s"]],[[0,1,"pl-s"],[1,10,"pl-c1"],[10,11,"pl-s"],[14,15,"pl-s"],[15,26,"pl-c1"],[26,27,"pl-s"],[30,31,"pl-s"],[31,39,"pl-c1"],[39,40,"pl-s"],[42,43,"pl-s"],[43,70,"pl-c1"],[70,71,"pl-s"]],[],[[0,1,"pl-s"],[1,24,"pl-c1"],[24,25,"pl-s"]],[],[[33,34,"pl-s"],[34,38,"pl-c1"],[38,39,"pl-s"],[41,42,"pl-s"],[42,52,"pl-c1"],[52,53,"pl-s"],[55,56,"pl-s"],[56,66,"pl-c1"],[66,67,"pl-s"],[69,70,"pl-s"],[70,73,"pl-c1"],[73,74,"pl-s"]],[[0,1,"pl-s"],[1,6,"pl-c1"],[6,7,"pl-s"],[9,10,"pl-s"],[10,14,"pl-c1"],[14,15,"pl-s"],[17,18,"pl-s"],[18,28,"pl-c1"],[28,29,"pl-s"],[61,62,"pl-s"],[62,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-s"],[1,9,"pl-c1"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[3,9,"pl-en"]],[],[[0,1,"pl-s"],[1,17,"pl-c1"],[17,18,"pl-s"]],[],[[0,1,"pl-v"],[2,3,"pl-s"],[3,13,"pl-c1"],[13,14,"pl-s"],[22,23,"pl-s"],[23,49,"pl-c1"],[49,50,"pl-s"],[56,57,"pl-s"],[57,82,"pl-c1"],[82,83,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[27,28,"pl-s"],[28,38,"pl-c1"],[38,39,"pl-s"],[60,61,"pl-s"],[61,75,"pl-c1"],[75,76,"pl-s"]],[[7,8,"pl-s"],[8,18,"pl-c1"],[18,19,"pl-s"],[24,25,"pl-s"],[25,35,"pl-c1"],[35,36,"pl-s"],[38,39,"pl-s"],[39,52,"pl-c1"],[52,53,"pl-s"]],[[2,3,"pl-s"],[3,50,"pl-c1"],[50,51,"pl-s"]],[[2,3,"pl-s"],[3,31,"pl-c1"],[31,32,"pl-s"]],[[2,3,"pl-s"],[3,44,"pl-c1"],[44,45,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[3,22,"pl-c1"],[22,23,"pl-s"]],[],[[0,22,"pl-mh"],[3,22,"pl-en"]],[],[[0,1,"pl-v"],[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[64,65,"pl-s"],[65,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"],[23,24,"pl-s"],[24,30,"pl-c1"],[30,31,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"],[33,34,"pl-s"],[34,36,"pl-c1"],[36,37,"pl-s"]],[],[[0,22,"pl-mh"],[3,22,"pl-en"]],[],[[0,1,"pl-v"],[7,9,"pl-s"],[11,13,"pl-s"],[34,35,"pl-s"],[35,53,"pl-c1"],[53,54,"pl-s"],[60,61,"pl-s"],[61,78,"pl-c1"],[78,79,"pl-s"]],[[35,36,"pl-s"],[36,39,"pl-s"],[37,39,"pl-corl"],[39,40,"pl-s"],[40,41,"pl-s"],[41,88,"pl-corl"],[88,89,"pl-s"]],[[0,1,"pl-v"],[13,15,"pl-s"],[17,19,"pl-s"],[21,22,"pl-s"],[22,39,"pl-c1"],[39,40,"pl-s"],[54,55,"pl-s"],[55,63,"pl-c1"],[63,64,"pl-s"]],[[2,3,"pl-s"],[3,10,"pl-c1"],[10,11,"pl-s"],[13,14,"pl-s"],[14,29,"pl-c1"],[29,30,"pl-s"],[32,33,"pl-s"],[33,45,"pl-c1"],[45,46,"pl-s"]],[[39,40,"pl-s"],[40,57,"pl-c1"],[57,58,"pl-s"]],[[2,3,"pl-s"],[3,13,"pl-c1"],[13,14,"pl-s"],[52,53,"pl-s"],[53,62,"pl-c1"],[62,63,"pl-s"]],[[47,49,"pl-s"]],[[7,9,"pl-s"],[11,12,"pl-s"],[12,37,"pl-c1"],[37,38,"pl-s"],[43,44,"pl-s"],[44,52,"pl-c1"],[52,53,"pl-s"],[56,57,"pl-s"],[57,64,"pl-c1"],[64,65,"pl-s"]],[],[],[[27,28,"pl-s"],[28,30,"pl-s"],[29,30,"pl-corl"],[30,31,"pl-s"],[31,32,"pl-s"],[32,78,"pl-corl"],[78,79,"pl-s"]],[[2,3,"pl-s"],[3,7,"pl-s"],[4,7,"pl-corl"],[7,8,"pl-s"],[8,9,"pl-s"],[9,57,"pl-corl"],[57,58,"pl-s"]],[[0,1,"pl-v"],[7,9,"pl-s"],[11,13,"pl-s"],[27,28,"pl-s"],[28,38,"pl-c1"],[38,39,"pl-s"],[41,42,"pl-s"],[42,60,"pl-c1"],[60,61,"pl-s"]],[[47,48,"pl-s"],[48,51,"pl-s"],[49,51,"pl-corl"],[51,52,"pl-s"],[52,53,"pl-s"],[53,100,"pl-corl"],[100,101,"pl-s"]],[[0,1,"pl-v"],[56,57,"pl-s"]],[],[[0,1,"pl-v"],[7,9,"pl-s"],[11,13,"pl-s"],[21,22,"pl-s"],[22,32,"pl-c1"],[32,33,"pl-s"],[36,37,"pl-s"],[37,45,"pl-c1"],[45,46,"pl-s"],[49,50,"pl-s"],[50,57,"pl-c1"],[57,58,"pl-s"],[65,66,"pl-s"],[66,71,"pl-c1"],[71,72,"pl-s"]],[[10,11,"pl-s"],[11,29,"pl-c1"],[29,30,"pl-s"],[32,33,"pl-s"],[33,36,"pl-s"],[34,36,"pl-corl"],[36,37,"pl-s"],[37,38,"pl-s"],[38,85,"pl-corl"],[85,86,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"]],[],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,20,"pl-c1"],[20,21,"pl-s"]],[[45,46,"pl-s"],[46,63,"pl-c1"],[63,64,"pl-s"]],[[3,4,"pl-s"],[4,10,"pl-c1"],[10,11,"pl-s"]],[[37,41,"pl-s"],[38,41,"pl-corl"],[54,55,"pl-s"],[55,67,"pl-c1"],[67,68,"pl-s"]],[[3,4,"pl-s"],[4,25,"pl-c1"],[25,26,"pl-s"],[36,37,"pl-s"],[37,45,"pl-c1"],[45,46,"pl-s"],[47,48,"pl-s"],[48,57,"pl-c1"],[57,58,"pl-s"],[62,63,"pl-s"],[63,75,"pl-c1"],[75,76,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,33,"pl-c1"],[33,34,"pl-s"]],[[3,4,"pl-s"],[4,21,"pl-c1"],[21,22,"pl-s"],[24,25,"pl-s"],[54,55,"pl-s"],[55,63,"pl-c1"],[63,64,"pl-s"]],[[3,4,"pl-s"],[4,11,"pl-c1"],[11,12,"pl-s"]],[[16,17,"pl-s"],[17,42,"pl-c1"],[42,43,"pl-s"],[48,49,"pl-s"],[49,57,"pl-c1"],[57,58,"pl-s"]],[[18,19,"pl-s"],[19,26,"pl-c1"],[26,27,"pl-s"]],[],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,32,"pl-c1"],[32,33,"pl-s"]],[],[],[[1,2,"pl-s"],[2,3,"pl-v"],[12,13,"pl-s"],[13,22,"pl-c1"],[22,23,"pl-s"],[26,27,"pl-s"],[27,56,"pl-c1"],[56,57,"pl-s"]],[[4,5,"pl-s"],[5,21,"pl-c1"],[21,22,"pl-s"]],[[28,31,"pl-s"],[29,31,"pl-corl"]],[[4,5,"pl-s"],[5,22,"pl-c1"],[22,23,"pl-s"]],[],[[0,1,"pl-s"],[1,2,"pl-v"]],[[7,8,"pl-s"],[8,27,"pl-c1"],[27,28,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"]],[],[[0,1,"pl-s"],[1,25,"pl-c1"],[25,26,"pl-s"]],[],[[0,1,"pl-s"],[1,2,"pl-v"]],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,33,"pl-c1"],[33,34,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,24,"pl-c1"],[24,25,"pl-s"],[57,58,"pl-s"],[58,76,"pl-c1"],[76,77,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,21,"pl-c1"],[21,22,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"],[4,45,"pl-c1"],[45,46,"pl-s"]],[],[],[[0,1,"pl-s"],[1,12,"pl-c1"],[12,13,"pl-s"]],[],[[0,18,"pl-mh"],[3,18,"pl-en"]],[],[[0,1,"pl-s"],[1,2,"pl-v"],[18,19,"pl-s"],[19,23,"pl-en"]],[[0,1,"pl-s"],[1,2,"pl-v"]],[[0,1,"pl-s"],[1,2,"pl-v"]],[[0,1,"pl-s"],[1,2,"pl-v"]],[[0,1,"pl-s"],[1,2,"pl-v"],[3,4,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"],[36,38,"pl-s"],[40,42,"pl-s"],[44,45,"pl-s"],[45,48,"pl-s"],[46,48,"pl-corl"],[48,49,"pl-s"],[49,50,"pl-s"],[50,97,"pl-corl"],[97,98,"pl-s"]],[[0,1,"pl-s"],[1,2,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[19,22,"pl-s"],[20,22,"pl-corl"]],[],[[25,28,"pl-s"],[26,28,"pl-corl"]],[[0,1,"pl-s"],[6,9,"pl-s"],[7,9,"pl-corl"],[9,10,"pl-s"],[10,11,"pl-s"],[11,58,"pl-corl"],[58,59,"pl-s"]],[[10,11,"pl-s"],[29,30,"pl-s"],[30,31,"pl-s"],[31,81,"pl-corl"],[81,82,"pl-s"]],[[11,12,"pl-s"],[27,28,"pl-s"],[28,29,"pl-s"],[29,44,"pl-corl"],[44,45,"pl-s"]],[],[[0,1,"pl-ml"],[8,9,"pl-ml"],[16,17,"pl-ml"],[22,23,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-ml"],[16,17,"pl-ml"],[22,23,"pl-ml"]],[[0,1,"pl-ml"],[4,5,"pl-ml"],[6,7,"pl-s"],[7,10,"pl-s"],[8,10,"pl-corl"],[10,11,"pl-s"],[11,12,"pl-s"],[12,59,"pl-corl"],[59,60,"pl-s"],[61,62,"pl-ml"],[63,65,"pl-s"],[67,69,"pl-s"],[72,73,"pl-s"],[73,89,"pl-c1"],[89,90,"pl-s"],[93,94,"pl-s"],[94,106,"pl-c1"],[106,107,"pl-s"],[142,143,"pl-s"],[143,150,"pl-c1"],[150,151,"pl-s"],[171,172,"pl-s"],[172,181,"pl-c1"],[181,182,"pl-s"],[246,247,"pl-ml"]],[[0,1,"pl-ml"],[4,5,"pl-ml"],[6,7,"pl-s"],[7,10,"pl-s"],[8,10,"pl-corl"],[10,11,"pl-s"],[11,12,"pl-s"],[12,59,"pl-corl"],[59,60,"pl-s"],[61,62,"pl-ml"],[63,65,"pl-s"],[67,69,"pl-s"],[72,73,"pl-s"],[73,80,"pl-c1"],[80,81,"pl-s"],[88,89,"pl-s"],[89,98,"pl-c1"],[98,99,"pl-s"],[102,103,"pl-s"],[103,114,"pl-c1"],[114,115,"pl-s"],[118,119,"pl-s"],[119,127,"pl-c1"],[127,128,"pl-s"],[138,139,"pl-ml"]],[[0,1,"pl-ml"],[4,5,"pl-ml"],[6,7,"pl-s"],[7,10,"pl-s"],[8,10,"pl-corl"],[10,11,"pl-s"],[11,12,"pl-s"],[12,59,"pl-corl"],[59,60,"pl-s"],[61,62,"pl-ml"],[63,65,"pl-s"],[67,69,"pl-s"],[72,73,"pl-s"],[73,85,"pl-c1"],[85,86,"pl-s"],[89,90,"pl-s"],[90,102,"pl-c1"],[102,103,"pl-s"],[109,110,"pl-s"],[110,120,"pl-c1"],[120,121,"pl-s"],[123,124,"pl-s"],[124,136,"pl-c1"],[136,137,"pl-s"],[146,147,"pl-s"],[147,166,"pl-c1"],[166,167,"pl-s"],[196,197,"pl-s"],[197,200,"pl-s"],[198,200,"pl-corl"],[200,201,"pl-s"],[201,202,"pl-s"],[202,248,"pl-corl"],[248,249,"pl-s"],[251,252,"pl-ml"]],[[0,1,"pl-ml"],[4,5,"pl-ml"],[6,7,"pl-s"],[7,10,"pl-s"],[8,10,"pl-corl"],[10,11,"pl-s"],[11,12,"pl-s"],[12,59,"pl-corl"],[59,60,"pl-s"],[61,62,"pl-ml"],[63,65,"pl-s"],[67,69,"pl-s"],[95,96,"pl-s"],[96,102,"pl-c1"],[102,103,"pl-s"],[106,107,"pl-s"],[107,110,"pl-c1"],[110,111,"pl-s"],[114,115,"pl-s"],[115,120,"pl-c1"],[120,121,"pl-s"],[124,125,"pl-ml"]],[[0,1,"pl-ml"],[4,5,"pl-ml"],[6,7,"pl-s"],[7,10,"pl-s"],[8,10,"pl-corl"],[10,11,"pl-s"],[11,12,"pl-s"],[12,59,"pl-corl"],[59,60,"pl-s"],[61,62,"pl-ml"],[63,65,"pl-s"],[67,69,"pl-s"],[89,90,"pl-s"],[90,107,"pl-c1"],[107,108,"pl-s"],[118,119,"pl-ml"]],[],[[5,7,"pl-s"],[10,12,"pl-s"],[40,41,"pl-s"],[41,43,"pl-s"],[42,43,"pl-corl"],[43,44,"pl-s"],[44,45,"pl-s"],[45,91,"pl-corl"],[91,92,"pl-s"],[98,99,"pl-s"],[99,102,"pl-s"],[100,102,"pl-corl"],[102,103,"pl-s"],[103,104,"pl-s"],[104,151,"pl-corl"],[151,152,"pl-s"],[158,159,"pl-s"],[159,162,"pl-s"],[160,162,"pl-corl"],[162,163,"pl-s"],[163,164,"pl-s"],[164,211,"pl-corl"],[211,212,"pl-s"],[217,219,"pl-s"],[221,223,"pl-s"]],[],[[0,128,"pl-mh"],[3,128,"pl-en"],[112,116,"pl-s"],[113,116,"pl-corl"],[123,127,"pl-s"],[124,127,"pl-corl"]],[],[[42,45,"pl-s"],[43,45,"pl-corl"]],[],[],[],[[39,40,"pl-s"],[40,54,"pl-c1"],[54,55,"pl-s"]],[[43,44,"pl-s"],[44,69,"pl-c1"],[69,70,"pl-s"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[117,118,"pl-s"],[118,119,"pl-s"],[217,218,"pl-ml"],[224,225,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,27,"pl-c1"],[27,28,"pl-s"],[30,31,"pl-s"],[31,38,"pl-c1"],[38,39,"pl-s"],[79,80,"pl-s"],[80,96,"pl-c1"],[96,97,"pl-s"],[119,120,"pl-s"],[120,141,"pl-c1"],[141,142,"pl-s"],[188,189,"pl-s"],[189,208,"pl-c1"],[208,209,"pl-s"],[237,238,"pl-s"],[238,259,"pl-c1"],[259,260,"pl-s"],[305,306,"pl-s"],[306,317,"pl-c1"],[317,318,"pl-s"],[342,343,"pl-ml"],[349,350,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,32,"pl-c1"],[32,33,"pl-s"],[200,201,"pl-ml"],[207,208,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,25,"pl-c1"],[25,26,"pl-s"],[79,80,"pl-s"],[80,94,"pl-c1"],[94,95,"pl-s"],[265,266,"pl-ml"],[274,275,"pl-s"],[275,305,"pl-c1"],[305,306,"pl-s"],[313,314,"pl-ml"]],[[0,1,"pl-ml"],[18,21,"pl-s"],[19,21,"pl-corl"],[22,26,"pl-s"],[23,26,"pl-corl"],[28,29,"pl-s"],[29,53,"pl-c1"],[53,54,"pl-s"],[57,58,"pl-s"],[58,81,"pl-c1"],[81,82,"pl-s"],[101,102,"pl-ml"],[108,109,"pl-ml"]],[[0,1,"pl-ml"],[76,80,"pl-s"],[77,80,"pl-corl"],[92,93,"pl-ml"],[99,100,"pl-ml"]],[],[[14,15,"pl-s"],[15,46,"pl-c1"],[46,47,"pl-s"],[55,56,"pl-s"],[56,80,"pl-c1"],[80,81,"pl-s"]],[[0,1,"pl-s"],[1,26,"pl-c1"],[26,27,"pl-s"],[39,40,"pl-s"],[40,68,"pl-c1"],[68,69,"pl-s"],[74,75,"pl-s"],[75,79,"pl-c1"],[79,80,"pl-s"]],[],[[0,88,"pl-mh"],[3,88,"pl-en"],[72,76,"pl-s"],[73,76,"pl-corl"],[83,87,"pl-s"],[84,87,"pl-corl"]],[],[],[],[[70,74,"pl-s"],[71,74,"pl-corl"]],[[8,9,"pl-s"],[40,41,"pl-s"],[41,42,"pl-s"],[42,73,"pl-corl"],[73,74,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[21,22,"pl-s"],[22,33,"pl-c1"],[33,34,"pl-s"],[36,37,"pl-s"],[37,61,"pl-c1"],[61,62,"pl-s"],[105,106,"pl-s"],[106,116,"pl-c1"],[116,117,"pl-s"],[209,210,"pl-ml"],[216,217,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[124,128,"pl-s"],[125,128,"pl-corl"],[188,189,"pl-ml"],[195,196,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,31,"pl-c1"],[31,32,"pl-s"],[121,122,"pl-s"],[122,134,"pl-c1"],[134,135,"pl-s"],[197,198,"pl-ml"],[204,205,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,23,"pl-c1"],[23,24,"pl-s"],[160,161,"pl-ml"],[167,168,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,23,"pl-c1"],[23,24,"pl-s"],[164,165,"pl-ml"],[171,172,"pl-ml"]],[[0,1,"pl-ml"],[250,251,"pl-ml"],[257,258,"pl-ml"]],[[0,1,"pl-ml"],[98,99,"pl-ml"],[105,106,"pl-ml"]],[[0,1,"pl-ml"],[123,124,"pl-ml"],[130,131,"pl-ml"]],[],[[14,15,"pl-s"],[15,28,"pl-c1"],[28,29,"pl-s"],[32,33,"pl-s"],[33,45,"pl-c1"],[45,46,"pl-s"]],[[0,1,"pl-s"],[1,59,"pl-c1"],[59,60,"pl-s"]],[[0,1,"pl-s"],[1,58,"pl-c1"],[58,59,"pl-s"]],[[7,8,"pl-s"],[8,28,"pl-c1"],[28,29,"pl-s"],[34,35,"pl-s"],[35,39,"pl-c1"],[39,40,"pl-s"]],[],[[0,83,"pl-mh"],[3,83,"pl-en"],[67,71,"pl-s"],[68,71,"pl-corl"],[78,82,"pl-s"],[79,82,"pl-corl"]],[],[[54,58,"pl-s"],[55,58,"pl-corl"],[71,75,"pl-s"],[72,75,"pl-corl"]],[],[],[],[],[[0,1,"pl-s"],[28,29,"pl-s"],[29,30,"pl-s"],[30,57,"pl-corl"],[57,58,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,19,"pl-c1"],[19,20,"pl-s"],[257,258,"pl-ml"],[264,265,"pl-ml"]],[[0,1,"pl-ml"],[26,27,"pl-s"],[27,52,"pl-c1"],[52,53,"pl-s"],[149,150,"pl-ml"],[156,157,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,29,"pl-c1"],[29,30,"pl-s"],[44,45,"pl-s"],[45,53,"pl-c1"],[53,54,"pl-s"],[69,70,"pl-s"],[70,79,"pl-c1"],[79,80,"pl-s"],[82,83,"pl-s"],[83,98,"pl-c1"],[98,99,"pl-s"],[101,102,"pl-s"],[102,109,"pl-c1"],[109,110,"pl-s"],[112,113,"pl-s"],[113,119,"pl-c1"],[119,120,"pl-s"],[122,123,"pl-s"],[123,136,"pl-c1"],[136,137,"pl-s"],[157,158,"pl-ml"],[164,165,"pl-ml"]],[[0,1,"pl-ml"],[162,163,"pl-ml"],[169,170,"pl-ml"]],[[0,1,"pl-ml"],[14,31,"pl-corl"],[34,67,"pl-corl"],[140,141,"pl-ml"],[147,148,"pl-ml"]],[[0,1,"pl-ml"],[54,55,"pl-ml"],[61,62,"pl-ml"]],[],[[14,15,"pl-s"],[15,28,"pl-c1"],[28,29,"pl-s"],[32,33,"pl-s"],[33,45,"pl-c1"],[45,46,"pl-s"]],[[0,1,"pl-s"],[1,79,"pl-c1"],[79,80,"pl-s"]],[[20,21,"pl-s"],[21,37,"pl-c1"],[37,38,"pl-s"],[43,44,"pl-s"],[44,48,"pl-c1"],[48,49,"pl-s"]],[],[[0,85,"pl-mh"],[3,85,"pl-en"],[69,73,"pl-s"],[70,73,"pl-corl"],[80,84,"pl-s"],[81,84,"pl-corl"]],[],[[36,37,"pl-s"],[37,40,"pl-s"],[38,40,"pl-corl"],[40,41,"pl-s"],[41,42,"pl-s"],[42,89,"pl-corl"],[89,90,"pl-s"]],[[0,1,"pl-s"],[1,15,"pl-c1"],[15,16,"pl-s"],[56,58,"pl-s"],[65,67,"pl-s"]],[[0,2,"pl-s"],[25,27,"pl-s"]],[[24,25,"pl-s"],[55,56,"pl-s"],[56,57,"pl-s"],[57,87,"pl-corl"],[87,88,"pl-s"]],[[7,11,"pl-s"],[8,11,"pl-corl"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,13,"pl-c1"],[13,14,"pl-s"],[26,27,"pl-s"],[27,40,"pl-c1"],[40,41,"pl-s"],[44,45,"pl-s"],[45,52,"pl-c1"],[52,53,"pl-s"],[125,126,"pl-s"],[126,147,"pl-c1"],[147,148,"pl-s"],[150,151,"pl-s"],[151,177,"pl-c1"],[177,178,"pl-s"],[180,181,"pl-s"],[181,207,"pl-c1"],[207,208,"pl-s"],[240,241,"pl-ml"],[247,248,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,30,"pl-c1"],[30,31,"pl-s"],[44,45,"pl-s"],[45,57,"pl-c1"],[57,58,"pl-s"],[103,104,"pl-s"],[104,113,"pl-c1"],[113,114,"pl-s"],[185,186,"pl-s"],[186,199,"pl-c1"],[199,200,"pl-s"],[278,279,"pl-s"],[279,289,"pl-c1"],[289,290,"pl-s"],[291,292,"pl-ml"],[298,299,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,26,"pl-c1"],[26,27,"pl-s"],[35,36,"pl-s"],[36,49,"pl-c1"],[49,50,"pl-s"],[51,52,"pl-s"],[52,66,"pl-c1"],[66,67,"pl-s"],[204,205,"pl-ml"],[211,212,"pl-ml"]],[[0,1,"pl-ml"],[230,231,"pl-ml"],[262,263,"pl-ml"]],[[0,1,"pl-ml"],[73,74,"pl-ml"],[80,81,"pl-ml"]],[[0,1,"pl-ml"],[50,51,"pl-ml"],[52,56,"pl-s"],[53,56,"pl-corl"],[69,70,"pl-ml"]],[],[[14,15,"pl-s"],[15,79,"pl-c1"],[79,80,"pl-s"]],[[13,14,"pl-s"],[22,23,"pl-s"],[23,35,"pl-c1"],[35,36,"pl-s"],[39,40,"pl-s"],[40,53,"pl-c1"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,26,"pl-c1"],[26,27,"pl-s"],[53,56,"pl-s"],[54,56,"pl-corl"]],[[7,8,"pl-s"],[8,29,"pl-c1"],[29,30,"pl-s"],[35,36,"pl-s"],[36,40,"pl-c1"],[40,41,"pl-s"]],[],[[0,51,"pl-mh"],[3,51,"pl-en"],[47,50,"pl-s"],[48,50,"pl-corl"]],[],[],[[21,22,"pl-s"],[22,29,"pl-c1"],[29,30,"pl-s"],[39,40,"pl-s"],[40,65,"pl-c1"],[65,66,"pl-s"]],[[10,11,"pl-s"],[11,37,"pl-c1"],[37,38,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[29,32,"pl-s"],[30,32,"pl-corl"],[43,44,"pl-ml"],[50,51,"pl-ml"]],[[0,1,"pl-ml"],[18,19,"pl-s"],[19,42,"pl-c1"],[42,43,"pl-s"],[66,67,"pl-ml"],[73,74,"pl-ml"]],[[0,1,"pl-ml"],[76,77,"pl-ml"],[83,84,"pl-ml"]],[[0,1,"pl-ml"],[69,70,"pl-ml"],[76,77,"pl-ml"]],[[0,1,"pl-ml"],[65,66,"pl-ml"],[77,80,"pl-s"],[78,80,"pl-corl"],[89,90,"pl-s"],[90,97,"pl-c1"],[97,98,"pl-s"],[99,100,"pl-ml"]],[[0,1,"pl-ml"],[76,77,"pl-ml"],[88,90,"pl-s"],[89,90,"pl-corl"],[92,93,"pl-s"],[93,100,"pl-c1"],[100,101,"pl-s"],[107,109,"pl-s"],[108,109,"pl-corl"],[111,112,"pl-s"],[112,119,"pl-c1"],[119,120,"pl-s"],[122,123,"pl-ml"]],[[0,1,"pl-ml"],[60,61,"pl-ml"],[108,111,"pl-s"],[109,111,"pl-corl"],[119,120,"pl-s"],[120,127,"pl-c1"],[127,128,"pl-s"],[129,130,"pl-ml"]],[],[],[],[],[[20,23,"pl-s"],[21,23,"pl-corl"]],[[1,2,"pl-s"],[2,7,"pl-c1"],[7,8,"pl-s"],[21,22,"pl-s"],[22,26,"pl-c1"],[26,27,"pl-s"],[46,47,"pl-s"],[47,62,"pl-c1"],[62,63,"pl-s"]],[],[],[],[],[[12,13,"pl-s"],[13,14,"pl-s"]],[],[[0,50,"pl-mh"],[3,50,"pl-en"],[36,39,"pl-s"],[37,39,"pl-corl"],[46,49,"pl-s"],[47,49,"pl-corl"]],[],[[21,22,"pl-s"],[22,31,"pl-c1"],[31,32,"pl-s"]],[],[[0,1,"pl-s"],[1,36,"pl-c1"],[36,37,"pl-s"],[43,44,"pl-s"],[44,59,"pl-c1"],[59,60,"pl-s"],[62,63,"pl-s"],[63,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-s"],[1,8,"pl-c1"],[8,9,"pl-s"],[11,12,"pl-s"],[12,19,"pl-c1"],[19,20,"pl-s"],[22,23,"pl-s"],[23,29,"pl-c1"],[29,30,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[74,75,"pl-ml"],[95,96,"pl-ml"]],[[0,1,"pl-ml"],[105,106,"pl-ml"],[152,153,"pl-ml"]],[[0,1,"pl-ml"],[12,13,"pl-s"],[13,33,"pl-c1"],[33,34,"pl-s"],[56,57,"pl-s"],[57,73,"pl-c1"],[73,74,"pl-s"],[75,76,"pl-ml"],[82,83,"pl-ml"]],[[0,1,"pl-ml"],[17,18,"pl-ml"],[390,391,"pl-s"],[424,425,"pl-s"],[426,427,"pl-ml"]],[],[],[[19,20,"pl-s"],[20,30,"pl-c1"],[30,31,"pl-s"],[40,43,"pl-s"],[41,43,"pl-corl"],[53,54,"pl-s"],[54,80,"pl-c1"],[80,81,"pl-s"]],[],[[0,66,"pl-mh"],[3,66,"pl-en"],[52,55,"pl-s"],[53,55,"pl-corl"],[62,65,"pl-s"],[63,65,"pl-corl"]],[],[[14,15,"pl-s"],[15,34,"pl-c1"],[34,35,"pl-s"],[41,42,"pl-s"],[42,61,"pl-c1"],[61,62,"pl-s"]],[[0,1,"pl-s"],[1,18,"pl-c1"],[18,19,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[93,94,"pl-s"],[94,98,"pl-c1"],[98,99,"pl-s"],[103,104,"pl-s"],[104,131,"pl-c1"],[131,132,"pl-s"],[133,134,"pl-ml"],[140,141,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,11,"pl-c1"],[11,12,"pl-s"],[17,18,"pl-s"],[18,30,"pl-c1"],[30,31,"pl-s"],[34,35,"pl-s"],[35,48,"pl-c1"],[48,49,"pl-s"],[144,145,"pl-ml"],[151,152,"pl-ml"]],[[0,1,"pl-ml"],[14,15,"pl-s"],[15,33,"pl-c1"],[33,34,"pl-s"],[79,80,"pl-ml"],[86,87,"pl-ml"]],[[0,1,"pl-ml"],[21,22,"pl-s"],[22,29,"pl-c1"],[29,30,"pl-s"],[31,32,"pl-s"],[32,39,"pl-c1"],[39,40,"pl-s"],[41,42,"pl-s"],[42,59,"pl-c1"],[59,60,"pl-s"],[61,62,"pl-s"],[62,69,"pl-c1"],[69,70,"pl-s"],[71,72,"pl-s"],[72,82,"pl-c1"],[82,83,"pl-s"],[95,96,"pl-s"],[96,112,"pl-c1"],[112,113,"pl-s"],[114,115,"pl-s"],[115,134,"pl-c1"],[134,135,"pl-s"],[145,146,"pl-ml"],[152,153,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[44,45,"pl-ml"],[51,52,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,36,"pl-c1"],[36,37,"pl-s"],[40,41,"pl-s"],[41,88,"pl-c1"],[88,89,"pl-s"],[96,97,"pl-ml"],[103,104,"pl-ml"]],[[0,1,"pl-ml"],[56,57,"pl-s"],[111,112,"pl-ml"],[129,130,"pl-s"],[130,141,"pl-c1"],[141,142,"pl-s"],[143,144,"pl-ml"]],[],[[7,8,"pl-s"],[8,29,"pl-c1"],[29,30,"pl-s"]],[[0,63,"pl-mh"],[3,63,"pl-en"],[48,52,"pl-s"],[49,52,"pl-corl"],[59,62,"pl-s"],[60,62,"pl-corl"]],[],[[48,49,"pl-s"],[49,65,"pl-c1"],[65,66,"pl-s"]],[[30,31,"pl-s"],[31,35,"pl-c1"],[35,36,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[14,15,"pl-s"],[15,44,"pl-c1"],[44,45,"pl-s"],[48,49,"pl-s"],[49,78,"pl-c1"],[78,79,"pl-s"],[95,96,"pl-s"],[96,104,"pl-c1"],[104,105,"pl-s"],[110,111,"pl-s"],[111,123,"pl-c1"],[123,124,"pl-s"],[127,128,"pl-s"],[128,138,"pl-c1"],[138,139,"pl-s"],[140,141,"pl-ml"],[147,148,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[37,38,"pl-s"],[38,42,"pl-c1"],[42,43,"pl-s"],[64,65,"pl-s"],[65,69,"pl-c1"],[69,70,"pl-s"],[100,101,"pl-s"],[101,113,"pl-c1"],[113,114,"pl-s"],[147,148,"pl-s"],[148,155,"pl-c1"],[155,156,"pl-s"],[159,160,"pl-s"],[160,176,"pl-c1"],[176,177,"pl-s"],[178,179,"pl-ml"],[185,186,"pl-ml"]],[[0,1,"pl-ml"],[13,14,"pl-s"],[14,18,"pl-c1"],[18,19,"pl-s"],[42,43,"pl-s"],[43,53,"pl-c1"],[53,54,"pl-s"],[63,64,"pl-s"],[64,72,"pl-c1"],[72,73,"pl-s"],[82,83,"pl-s"],[83,90,"pl-c1"],[90,91,"pl-s"],[96,97,"pl-s"],[97,109,"pl-c1"],[109,110,"pl-s"],[116,117,"pl-s"],[117,147,"pl-c1"],[147,148,"pl-s"],[192,196,"pl-s"],[193,196,"pl-corl"],[234,235,"pl-ml"],[264,265,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,77,"pl-c1"],[77,78,"pl-s"],[81,82,"pl-s"],[82,96,"pl-c1"],[96,97,"pl-s"],[105,106,"pl-s"],[106,114,"pl-c1"],[114,115,"pl-s"],[123,124,"pl-s"],[124,130,"pl-c1"],[130,131,"pl-s"],[138,139,"pl-ml"],[145,146,"pl-ml"]],[],[[7,8,"pl-s"],[8,27,"pl-c1"],[27,28,"pl-s"],[39,40,"pl-s"],[40,50,"pl-c1"],[50,51,"pl-s"]],[[0,84,"pl-mh"],[3,84,"pl-en"],[69,73,"pl-s"],[70,73,"pl-corl"],[80,83,"pl-s"],[81,83,"pl-corl"]],[],[[44,48,"pl-s"],[45,48,"pl-corl"]],[[0,1,"pl-s"],[1,34,"pl-c1"],[34,35,"pl-s"]],[[0,1,"pl-s"],[1,15,"pl-c1"],[15,16,"pl-s"],[30,31,"pl-s"],[31,61,"pl-c1"],[61,62,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[91,92,"pl-s"],[92,120,"pl-c1"],[120,121,"pl-s"],[124,125,"pl-s"],[125,131,"pl-c1"],[131,132,"pl-s"],[146,147,"pl-s"],[147,162,"pl-c1"],[162,163,"pl-s"],[165,166,"pl-s"],[166,180,"pl-c1"],[180,181,"pl-s"],[183,184,"pl-s"],[184,214,"pl-c1"],[214,215,"pl-s"],[217,218,"pl-s"],[218,228,"pl-c1"],[228,229,"pl-s"],[230,231,"pl-s"],[231,238,"pl-c1"],[238,239,"pl-s"],[245,246,"pl-ml"],[252,253,"pl-ml"]],[[0,1,"pl-ml"],[41,42,"pl-s"],[42,58,"pl-c1"],[58,59,"pl-s"],[61,62,"pl-s"],[62,78,"pl-c1"],[78,79,"pl-s"],[107,111,"pl-s"],[108,111,"pl-corl"],[112,116,"pl-s"],[113,116,"pl-corl"],[117,121,"pl-s"],[118,121,"pl-corl"],[143,144,"pl-s"],[144,152,"pl-c1"],[152,153,"pl-s"],[154,155,"pl-s"],[155,162,"pl-c1"],[162,163,"pl-s"],[164,165,"pl-s"],[165,178,"pl-c1"],[178,179,"pl-s"],[204,205,"pl-ml"],[211,212,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-s"],[9,34,"pl-c1"],[34,35,"pl-s"],[38,39,"pl-s"],[39,51,"pl-c1"],[51,52,"pl-s"],[55,56,"pl-s"],[56,64,"pl-c1"],[64,65,"pl-s"],[90,91,"pl-s"],[91,113,"pl-c1"],[113,114,"pl-s"],[128,129,"pl-s"],[129,135,"pl-c1"],[135,136,"pl-s"],[137,138,"pl-s"],[138,144,"pl-c1"],[144,145,"pl-s"],[146,147,"pl-ml"],[153,154,"pl-ml"]],[[0,1,"pl-ml"],[20,21,"pl-s"],[21,34,"pl-c1"],[34,35,"pl-s"],[38,39,"pl-s"],[39,55,"pl-c1"],[55,56,"pl-s"],[59,60,"pl-s"],[60,75,"pl-c1"],[75,76,"pl-s"],[79,80,"pl-s"],[80,94,"pl-c1"],[94,95,"pl-s"],[97,98,"pl-s"],[98,110,"pl-c1"],[110,111,"pl-s"],[120,121,"pl-s"],[121,125,"pl-c1"],[125,126,"pl-s"],[145,146,"pl-s"],[146,152,"pl-c1"],[152,153,"pl-s"],[154,155,"pl-ml"],[161,162,"pl-ml"]],[[0,1,"pl-ml"],[40,41,"pl-s"],[41,55,"pl-c1"],[55,56,"pl-s"],[58,59,"pl-s"],[59,66,"pl-c1"],[66,67,"pl-s"],[69,70,"pl-s"],[70,86,"pl-c1"],[86,87,"pl-s"],[107,108,"pl-s"],[108,119,"pl-c1"],[119,120,"pl-s"],[133,134,"pl-ml"],[140,141,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[29,30,"pl-s"],[30,53,"pl-c1"],[53,54,"pl-s"],[62,63,"pl-s"],[63,69,"pl-c1"],[69,70,"pl-s"],[78,79,"pl-s"],[79,87,"pl-c1"],[87,88,"pl-s"],[130,131,"pl-ml"],[137,138,"pl-ml"]],[[0,1,"pl-ml"],[9,10,"pl-s"],[10,36,"pl-c1"],[36,37,"pl-s"],[42,43,"pl-s"],[43,60,"pl-c1"],[60,61,"pl-s"],[72,73,"pl-s"],[73,83,"pl-c1"],[83,84,"pl-s"],[95,96,"pl-ml"],[121,122,"pl-ml"]],[],[[7,8,"pl-s"],[8,34,"pl-c1"],[34,35,"pl-s"],[46,47,"pl-s"],[47,57,"pl-c1"],[57,58,"pl-s"]],[],[[0,72,"pl-mh"],[3,72,"pl-en"],[58,61,"pl-s"],[59,61,"pl-corl"],[68,71,"pl-s"],[69,71,"pl-corl"]],[],[[12,13,"pl-s"],[13,39,"pl-c1"],[39,40,"pl-s"]],[[0,1,"pl-s"],[1,28,"pl-c1"],[28,29,"pl-s"],[64,65,"pl-s"],[65,79,"pl-c1"],[79,80,"pl-s"]],[[7,8,"pl-s"],[8,12,"pl-c1"],[12,13,"pl-s"],[14,15,"pl-s"],[15,25,"pl-c1"],[25,26,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[110,111,"pl-ml"],[119,120,"pl-s"],[120,125,"pl-c1"],[125,126,"pl-s"],[133,134,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-s"],[9,23,"pl-c1"],[23,24,"pl-s"],[27,28,"pl-s"],[28,36,"pl-c1"],[36,37,"pl-s"],[39,40,"pl-s"],[40,46,"pl-c1"],[46,47,"pl-s"],[48,49,"pl-s"],[49,57,"pl-c1"],[57,58,"pl-s"],[71,72,"pl-s"],[72,83,"pl-c1"],[83,84,"pl-s"],[85,86,"pl-s"],[86,98,"pl-c1"],[98,99,"pl-s"],[111,112,"pl-ml"],[118,119,"pl-ml"]],[[0,1,"pl-ml"],[10,11,"pl-s"],[11,26,"pl-c1"],[26,27,"pl-s"],[30,31,"pl-s"],[31,40,"pl-c1"],[40,41,"pl-s"],[67,68,"pl-s"],[68,85,"pl-c1"],[85,86,"pl-s"],[95,96,"pl-ml"],[102,103,"pl-ml"]],[[0,1,"pl-ml"],[26,27,"pl-s"],[27,30,"pl-c1"],[30,31,"pl-s"],[62,63,"pl-ml"],[69,70,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[22,23,"pl-s"],[23,50,"pl-c1"],[50,51,"pl-s"],[56,57,"pl-ml"],[63,64,"pl-ml"]],[[0,1,"pl-ml"],[10,13,"pl-s"],[11,13,"pl-corl"],[21,22,"pl-ml"],[28,29,"pl-ml"]],[],[[20,21,"pl-s"],[21,31,"pl-c1"],[31,32,"pl-s"],[33,34,"pl-s"],[34,41,"pl-c1"],[41,42,"pl-s"],[50,53,"pl-s"],[51,53,"pl-corl"],[63,64,"pl-s"],[64,79,"pl-c1"],[79,80,"pl-s"]],[[41,42,"pl-s"],[42,60,"pl-c1"],[60,61,"pl-s"]],[[0,1,"pl-s"],[1,7,"pl-c1"],[7,8,"pl-s"],[26,27,"pl-s"],[27,44,"pl-c1"],[44,45,"pl-s"],[51,52,"pl-s"],[52,74,"pl-c1"],[74,75,"pl-s"]],[[27,28,"pl-s"],[28,33,"pl-c1"],[33,34,"pl-s"]],[[0,69,"pl-mh"],[3,69,"pl-en"],[55,58,"pl-s"],[56,58,"pl-corl"],[65,68,"pl-s"],[66,68,"pl-corl"]],[],[[13,14,"pl-s"],[14,29,"pl-c1"],[29,30,"pl-s"],[70,71,"pl-s"],[71,79,"pl-c1"],[79,80,"pl-s"]],[[30,31,"pl-s"],[31,40,"pl-c1"],[40,41,"pl-s"],[58,59,"pl-s"],[59,60,"pl-s"],[74,75,"pl-s"],[75,76,"pl-s"]],[[8,9,"pl-s"],[9,10,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,46,"pl-c1"],[46,47,"pl-s"],[50,51,"pl-s"],[51,66,"pl-c1"],[66,67,"pl-s"],[87,88,"pl-ml"],[94,95,"pl-ml"]],[[0,1,"pl-ml"],[113,114,"pl-ml"],[120,121,"pl-ml"]],[[0,1,"pl-ml"],[43,44,"pl-s"],[44,67,"pl-c1"],[67,68,"pl-s"],[71,72,"pl-s"],[72,81,"pl-c1"],[81,82,"pl-s"],[84,85,"pl-s"],[85,94,"pl-c1"],[94,95,"pl-s"],[102,103,"pl-s"],[103,109,"pl-c1"],[109,110,"pl-s"],[111,112,"pl-ml"],[120,121,"pl-s"],[121,142,"pl-c1"],[142,143,"pl-s"],[150,151,"pl-s"],[151,162,"pl-c1"],[162,163,"pl-s"],[166,167,"pl-s"],[167,176,"pl-c1"],[176,177,"pl-s"],[198,199,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[48,49,"pl-s"],[49,69,"pl-c1"],[69,70,"pl-s"],[92,93,"pl-ml"],[99,100,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[42,43,"pl-s"],[43,62,"pl-c1"],[62,63,"pl-s"],[64,65,"pl-ml"],[71,72,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,23,"pl-c1"],[23,24,"pl-s"],[40,41,"pl-s"],[41,50,"pl-c1"],[50,51,"pl-s"],[60,61,"pl-ml"],[67,68,"pl-ml"]],[[0,1,"pl-ml"],[16,17,"pl-s"],[17,25,"pl-c1"],[25,26,"pl-s"],[41,42,"pl-s"],[42,56,"pl-c1"],[56,57,"pl-s"],[84,85,"pl-s"],[85,95,"pl-c1"],[95,96,"pl-s"],[112,113,"pl-s"],[113,114,"pl-s"],[115,116,"pl-ml"],[124,125,"pl-s"],[125,163,"pl-c1"],[163,164,"pl-s"],[171,172,"pl-ml"]],[],[[7,8,"pl-s"],[8,30,"pl-c1"],[30,31,"pl-s"],[42,43,"pl-s"],[43,59,"pl-c1"],[59,60,"pl-s"]],[],[[0,1,"pl-s"],[1,5,"pl-c1"],[5,6,"pl-s"],[23,24,"pl-s"],[24,32,"pl-c1"],[32,33,"pl-s"],[44,47,"pl-s"],[45,47,"pl-corl"],[48,49,"pl-s"],[49,58,"pl-c1"],[58,59,"pl-s"]],[[4,5,"pl-s"],[5,10,"pl-c1"],[10,11,"pl-s"]],[],[[0,72,"pl-mh"],[3,72,"pl-en"],[58,61,"pl-s"],[59,61,"pl-corl"],[68,71,"pl-s"],[69,71,"pl-corl"]],[],[[45,46,"pl-s"],[46,55,"pl-c1"],[55,56,"pl-s"]],[[9,10,"pl-s"],[10,24,"pl-c1"],[24,25,"pl-s"],[28,29,"pl-s"],[29,40,"pl-c1"],[40,41,"pl-s"]],[[0,1,"pl-s"],[1,10,"pl-c1"],[10,11,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[22,23,"pl-s"],[23,29,"pl-c1"],[29,30,"pl-s"],[52,53,"pl-s"],[53,57,"pl-c1"],[57,58,"pl-s"],[59,60,"pl-s"],[60,63,"pl-c1"],[63,64,"pl-s"],[90,91,"pl-s"],[91,106,"pl-c1"],[106,107,"pl-s"],[112,113,"pl-s"],[113,133,"pl-c1"],[133,134,"pl-s"],[144,145,"pl-ml"],[151,152,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[22,23,"pl-s"],[23,29,"pl-c1"],[29,30,"pl-s"],[31,32,"pl-s"],[32,39,"pl-c1"],[39,40,"pl-s"],[79,80,"pl-s"],[80,84,"pl-c1"],[84,85,"pl-s"],[107,108,"pl-s"],[108,139,"pl-c1"],[139,140,"pl-s"],[142,143,"pl-s"],[143,167,"pl-c1"],[167,168,"pl-s"],[170,171,"pl-s"],[171,192,"pl-c1"],[192,193,"pl-s"],[194,195,"pl-ml"],[201,202,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,19,"pl-c1"],[19,20,"pl-s"],[21,22,"pl-s"],[22,29,"pl-c1"],[29,30,"pl-s"],[31,32,"pl-s"],[32,34,"pl-c1"],[34,35,"pl-s"],[38,39,"pl-s"],[39,55,"pl-c1"],[55,56,"pl-s"],[58,59,"pl-s"],[59,73,"pl-c1"],[73,74,"pl-s"],[83,84,"pl-ml"],[90,91,"pl-ml"]],[[0,1,"pl-ml"],[16,17,"pl-s"],[17,50,"pl-c1"],[50,51,"pl-s"],[82,83,"pl-ml"],[91,92,"pl-s"],[92,130,"pl-c1"],[130,131,"pl-s"],[139,140,"pl-s"],[140,146,"pl-c1"],[146,147,"pl-s"],[179,180,"pl-ml"]],[],[[7,8,"pl-s"],[8,19,"pl-c1"],[19,20,"pl-s"],[33,34,"pl-s"],[34,43,"pl-c1"],[43,44,"pl-s"],[45,46,"pl-s"],[46,54,"pl-c1"],[54,55,"pl-s"]],[],[[0,1,"pl-s"],[1,15,"pl-c1"],[15,16,"pl-s"],[17,18,"pl-s"],[18,29,"pl-c1"],[29,30,"pl-s"],[47,48,"pl-s"],[48,62,"pl-c1"],[62,63,"pl-s"],[65,66,"pl-s"],[66,79,"pl-c1"],[79,80,"pl-s"]],[[16,17,"pl-s"],[17,32,"pl-c1"],[32,33,"pl-s"]],[],[[0,74,"pl-mh"],[3,74,"pl-en"],[59,63,"pl-s"],[60,63,"pl-corl"],[70,73,"pl-s"],[71,73,"pl-corl"]],[],[[50,51,"pl-s"],[51,63,"pl-c1"],[63,64,"pl-s"],[66,67,"pl-s"]],[],[],[[25,26,"pl-s"],[26,37,"pl-c1"],[37,38,"pl-s"],[48,50,"pl-s"],[56,58,"pl-s"]],[[0,1,"pl-s"],[1,8,"pl-c1"],[8,9,"pl-s"],[11,12,"pl-s"],[12,24,"pl-c1"],[24,25,"pl-s"]],[],[[28,29,"pl-s"],[29,41,"pl-c1"],[41,42,"pl-s"]],[],[[12,13,"pl-s"],[13,29,"pl-c1"],[29,30,"pl-s"]],[[0,1,"pl-s"],[1,63,"pl-c1"],[63,64,"pl-s"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,23,"pl-c1"],[23,24,"pl-s"],[140,141,"pl-ml"],[147,148,"pl-ml"]],[[0,1,"pl-ml"],[21,22,"pl-s"],[22,30,"pl-c1"],[30,31,"pl-s"],[76,77,"pl-s"],[77,95,"pl-c1"],[95,96,"pl-s"],[109,110,"pl-s"],[110,124,"pl-c1"],[124,125,"pl-s"],[153,154,"pl-ml"],[160,161,"pl-ml"]],[[0,1,"pl-ml"],[30,31,"pl-s"],[31,48,"pl-c1"],[48,49,"pl-s"],[131,132,"pl-ml"],[138,139,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,19,"pl-c1"],[19,20,"pl-s"],[134,138,"pl-s"],[135,138,"pl-corl"],[163,164,"pl-ml"],[170,171,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-s"],[9,32,"pl-c1"],[32,33,"pl-s"],[45,46,"pl-s"],[46,53,"pl-c1"],[53,54,"pl-s"],[130,131,"pl-ml"],[178,179,"pl-ml"]],[[0,1,"pl-ml"],[15,16,"pl-s"],[16,38,"pl-c1"],[38,39,"pl-s"],[41,42,"pl-s"],[42,81,"pl-c1"],[81,82,"pl-s"],[83,84,"pl-ml"],[97,98,"pl-s"],[98,103,"pl-c1"],[103,104,"pl-s"],[105,106,"pl-ml"]],[],[[8,9,"pl-s"],[9,73,"pl-c1"],[73,74,"pl-s"]],[[7,8,"pl-s"],[8,14,"pl-c1"],[14,15,"pl-s"],[36,37,"pl-s"],[37,56,"pl-c1"],[56,57,"pl-s"]],[],[[9,10,"pl-s"],[10,28,"pl-c1"],[28,29,"pl-s"]],[[39,43,"pl-s"],[40,43,"pl-corl"]],[],[[0,81,"pl-mh"],[3,81,"pl-en"],[67,70,"pl-s"],[68,70,"pl-corl"],[77,80,"pl-s"],[78,80,"pl-corl"]],[],[],[],[[46,49,"pl-s"],[47,49,"pl-corl"],[72,75,"pl-s"],[73,75,"pl-corl"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,27,"pl-c1"],[27,28,"pl-s"],[30,31,"pl-s"],[31,39,"pl-c1"],[39,40,"pl-s"],[73,74,"pl-s"],[74,121,"pl-c1"],[121,122,"pl-s"],[124,125,"pl-s"],[125,133,"pl-c1"],[133,134,"pl-s"],[165,166,"pl-s"],[166,172,"pl-c1"],[172,173,"pl-s"],[174,175,"pl-s"],[175,180,"pl-c1"],[180,181,"pl-s"],[190,191,"pl-s"],[191,194,"pl-c1"],[194,195,"pl-s"],[214,215,"pl-ml"],[221,222,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[28,29,"pl-s"],[29,95,"pl-c1"],[95,96,"pl-s"],[120,121,"pl-s"],[121,142,"pl-c1"],[142,143,"pl-s"],[153,154,"pl-s"],[154,156,"pl-c1"],[156,157,"pl-s"],[281,282,"pl-ml"],[288,289,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,33,"pl-c1"],[33,34,"pl-s"],[83,84,"pl-ml"],[90,91,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,32,"pl-c1"],[32,33,"pl-s"],[226,227,"pl-ml"],[235,236,"pl-s"],[236,264,"pl-c1"],[264,265,"pl-s"],[273,274,"pl-s"],[274,280,"pl-c1"],[280,281,"pl-s"],[301,302,"pl-ml"]],[],[[66,69,"pl-s"],[67,69,"pl-corl"],[70,73,"pl-s"],[71,73,"pl-corl"]],[[0,1,"pl-s"],[1,18,"pl-c1"],[18,19,"pl-s"],[24,25,"pl-s"],[25,30,"pl-c1"],[30,31,"pl-s"]],[[59,62,"pl-s"],[60,62,"pl-corl"]],[],[[0,86,"pl-mh"],[3,86,"pl-en"],[39,42,"pl-s"],[40,42,"pl-corl"],[72,75,"pl-s"],[73,75,"pl-corl"],[82,85,"pl-s"],[83,85,"pl-corl"]],[],[[14,15,"pl-s"],[15,21,"pl-c1"],[21,22,"pl-s"]],[[0,1,"pl-s"],[1,45,"pl-c1"],[45,46,"pl-s"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[21,22,"pl-s"],[22,75,"pl-c1"],[75,76,"pl-s"],[78,79,"pl-s"],[79,97,"pl-c1"],[97,98,"pl-s"],[100,101,"pl-s"],[101,107,"pl-c1"],[107,108,"pl-s"],[110,111,"pl-s"],[111,177,"pl-c1"],[177,178,"pl-s"],[242,243,"pl-ml"],[250,253,"pl-s"],[251,253,"pl-corl"],[255,256,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[42,43,"pl-s"],[43,47,"pl-c1"],[47,48,"pl-s"],[111,112,"pl-ml"],[119,122,"pl-s"],[120,122,"pl-corl"],[124,125,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[69,70,"pl-s"],[70,90,"pl-c1"],[90,91,"pl-s"],[163,164,"pl-s"],[164,175,"pl-c1"],[175,176,"pl-s"],[179,180,"pl-s"],[180,192,"pl-c1"],[192,193,"pl-s"],[194,195,"pl-ml"],[201,202,"pl-ml"]],[[0,1,"pl-ml"],[9,10,"pl-s"],[10,16,"pl-c1"],[16,17,"pl-s"],[90,91,"pl-s"],[91,125,"pl-c1"],[125,126,"pl-s"],[128,129,"pl-ml"],[135,136,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[56,57,"pl-s"],[57,61,"pl-c1"],[61,62,"pl-s"],[73,74,"pl-s"],[74,94,"pl-c1"],[94,95,"pl-s"],[116,117,"pl-s"],[117,127,"pl-c1"],[127,128,"pl-s"],[169,170,"pl-ml"],[176,177,"pl-ml"]],[[0,1,"pl-ml"],[10,11,"pl-s"],[11,20,"pl-c1"],[20,21,"pl-s"],[46,47,"pl-s"],[47,72,"pl-c1"],[72,73,"pl-s"],[76,77,"pl-s"],[77,100,"pl-c1"],[100,101,"pl-s"],[123,124,"pl-s"],[124,131,"pl-c1"],[131,132,"pl-s"],[261,262,"pl-ml"],[268,269,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,38,"pl-c1"],[38,39,"pl-s"],[178,179,"pl-s"],[179,196,"pl-c1"],[196,197,"pl-s"],[198,199,"pl-s"],[199,210,"pl-c1"],[210,211,"pl-s"],[224,225,"pl-ml"],[233,234,"pl-s"],[234,247,"pl-c1"],[247,248,"pl-s"],[256,257,"pl-s"],[257,263,"pl-c1"],[263,264,"pl-s"],[272,273,"pl-s"],[273,294,"pl-c1"],[294,295,"pl-s"],[303,304,"pl-ml"]],[],[[0,57,"pl-mh"],[3,57,"pl-en"],[52,56,"pl-s"],[53,56,"pl-corl"]],[],[[15,16,"pl-s"],[24,25,"pl-s"],[25,32,"pl-c1"],[32,33,"pl-s"],[40,41,"pl-s"],[41,52,"pl-c1"],[52,53,"pl-s"],[61,62,"pl-s"],[62,73,"pl-c1"],[73,74,"pl-s"]],[[12,13,"pl-s"],[13,32,"pl-c1"],[32,33,"pl-s"],[35,36,"pl-s"],[36,55,"pl-c1"],[55,56,"pl-s"]],[],[[0,1,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,32,"pl-c1"],[32,33,"pl-s"],[35,36,"pl-s"],[36,44,"pl-c1"],[44,45,"pl-s"],[91,92,"pl-s"],[92,108,"pl-c1"],[108,109,"pl-s"],[165,166,"pl-s"],[166,182,"pl-c1"],[182,183,"pl-s"],[208,209,"pl-s"],[209,221,"pl-c1"],[221,222,"pl-s"],[245,246,"pl-ml"],[252,253,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[35,36,"pl-s"],[36,61,"pl-c1"],[61,62,"pl-s"],[65,66,"pl-s"],[66,82,"pl-c1"],[82,83,"pl-s"],[86,87,"pl-s"],[87,97,"pl-c1"],[97,98,"pl-s"],[138,139,"pl-ml"],[145,146,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,42,"pl-c1"],[42,43,"pl-s"],[194,195,"pl-ml"],[203,204,"pl-s"],[204,260,"pl-c1"],[260,261,"pl-s"],[269,270,"pl-s"],[270,276,"pl-c1"],[276,277,"pl-s"],[323,324,"pl-ml"]],[],[[13,14,"pl-s"],[14,25,"pl-c1"],[25,26,"pl-s"],[36,37,"pl-s"],[37,53,"pl-c1"],[53,54,"pl-s"],[56,57,"pl-s"],[57,65,"pl-c1"],[65,66,"pl-s"]],[[0,1,"pl-s"],[1,12,"pl-c1"],[12,13,"pl-s"]],[[60,64,"pl-s"],[61,64,"pl-corl"]],[],[[0,70,"pl-mh"],[3,70,"pl-en"],[65,69,"pl-s"],[66,69,"pl-corl"]],[],[[18,22,"pl-s"],[19,22,"pl-corl"],[36,37,"pl-s"],[37,44,"pl-c1"],[44,45,"pl-s"],[47,48,"pl-s"],[48,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-s"],[1,11,"pl-c1"],[11,12,"pl-s"],[26,27,"pl-s"],[27,38,"pl-c1"],[38,39,"pl-s"],[42,43,"pl-s"],[43,62,"pl-c1"],[62,63,"pl-s"]],[[13,14,"pl-s"],[14,33,"pl-c1"],[33,34,"pl-s"]],[[9,10,"pl-s"],[25,26,"pl-s"],[66,67,"pl-s"],[67,78,"pl-c1"],[78,79,"pl-s"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,31,"pl-c1"],[31,32,"pl-s"],[47,48,"pl-s"],[48,62,"pl-c1"],[62,63,"pl-s"],[66,67,"pl-s"],[67,75,"pl-c1"],[75,76,"pl-s"],[89,90,"pl-s"],[90,105,"pl-c1"],[105,106,"pl-s"],[108,109,"pl-s"],[109,124,"pl-c1"],[124,125,"pl-s"],[217,218,"pl-ml"],[224,225,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"],[14,15,"pl-s"],[15,25,"pl-c1"],[25,26,"pl-s"],[33,34,"pl-s"],[34,50,"pl-c1"],[50,51,"pl-s"],[74,75,"pl-s"],[75,82,"pl-c1"],[82,83,"pl-s"],[132,133,"pl-s"],[133,143,"pl-c1"],[143,144,"pl-s"],[145,146,"pl-s"],[146,161,"pl-c1"],[161,162,"pl-s"],[181,182,"pl-s"],[182,191,"pl-c1"],[191,192,"pl-s"],[214,215,"pl-ml"],[221,222,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,11,"pl-c1"],[11,12,"pl-s"],[13,14,"pl-s"],[14,33,"pl-c1"],[33,34,"pl-s"],[48,49,"pl-s"],[49,52,"pl-c1"],[52,53,"pl-s"],[114,115,"pl-ml"],[121,122,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[28,29,"pl-s"],[29,38,"pl-c1"],[38,39,"pl-s"],[69,70,"pl-s"],[70,82,"pl-c1"],[82,83,"pl-s"],[97,98,"pl-ml"],[104,105,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[18,19,"pl-s"],[19,33,"pl-c1"],[33,34,"pl-s"],[52,53,"pl-s"],[53,87,"pl-c1"],[87,88,"pl-s"],[104,105,"pl-s"],[105,124,"pl-c1"],[124,125,"pl-s"],[127,128,"pl-ml"],[134,135,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,41,"pl-c1"],[41,42,"pl-s"],[106,107,"pl-s"],[107,117,"pl-c1"],[117,118,"pl-s"],[140,141,"pl-s"],[141,146,"pl-c1"],[146,147,"pl-s"],[175,179,"pl-s"],[176,179,"pl-corl"],[192,193,"pl-ml"],[199,200,"pl-ml"]],[],[[30,31,"pl-s"],[31,51,"pl-c1"],[51,52,"pl-s"]],[[0,1,"pl-s"],[1,33,"pl-c1"],[33,34,"pl-s"]],[[28,29,"pl-s"],[29,40,"pl-c1"],[40,41,"pl-s"],[42,43,"pl-s"],[43,53,"pl-c1"],[53,54,"pl-s"]],[[14,15,"pl-s"],[15,26,"pl-c1"],[26,27,"pl-s"],[28,29,"pl-s"],[29,39,"pl-c1"],[39,40,"pl-s"]],[[31,32,"pl-s"],[66,67,"pl-s"],[67,74,"pl-c1"],[74,75,"pl-s"]],[],[[0,82,"pl-mh"],[3,82,"pl-en"],[78,81,"pl-s"],[79,81,"pl-corl"]],[],[[39,40,"pl-s"],[40,54,"pl-c1"],[54,55,"pl-s"]],[[1,5,"pl-s"],[2,5,"pl-corl"],[6,10,"pl-s"],[7,10,"pl-corl"],[11,12,"pl-s"],[12,19,"pl-c1"],[19,20,"pl-s"],[35,36,"pl-s"],[36,53,"pl-c1"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,12,"pl-c1"],[12,13,"pl-s"],[29,30,"pl-s"],[30,48,"pl-c1"],[48,49,"pl-s"]],[[0,1,"pl-s"],[1,22,"pl-c1"],[22,23,"pl-s"]],[],[[48,49,"pl-s"],[49,62,"pl-c1"],[62,63,"pl-s"]],[],[[0,1,"pl-ml"],[12,13,"pl-ml"],[22,23,"pl-ml"],[40,41,"pl-ml"],[46,47,"pl-ml"]],[[0,1,"pl-ml"],[12,13,"pl-ml"],[22,23,"pl-ml"],[40,41,"pl-ml"],[46,47,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[22,23,"pl-s"],[23,26,"pl-c1"],[26,27,"pl-s"],[72,73,"pl-ml"],[80,81,"pl-ml"],[211,212,"pl-ml"],[247,248,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,23,"pl-c1"],[23,24,"pl-s"],[35,36,"pl-ml"],[43,44,"pl-ml"],[75,76,"pl-s"],[76,81,"pl-c1"],[81,82,"pl-s"],[124,125,"pl-ml"],[146,147,"pl-s"],[147,152,"pl-c1"],[152,153,"pl-s"],[167,168,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[25,26,"pl-s"],[26,37,"pl-c1"],[37,38,"pl-s"],[41,42,"pl-s"],[42,55,"pl-c1"],[55,56,"pl-s"],[59,60,"pl-s"],[60,76,"pl-c1"],[76,77,"pl-s"],[78,79,"pl-ml"],[80,82,"pl-s"],[94,96,"pl-s"],[97,98,"pl-ml"],[99,100,"pl-s"],[100,122,"pl-c1"],[122,123,"pl-s"],[136,137,"pl-s"],[137,140,"pl-c1"],[140,141,"pl-s"],[184,185,"pl-s"],[185,191,"pl-c1"],[191,192,"pl-s"],[222,223,"pl-s"],[223,229,"pl-c1"],[229,230,"pl-s"],[243,244,"pl-ml"],[245,246,"pl-s"],[246,251,"pl-c1"],[251,252,"pl-s"],[255,256,"pl-s"],[256,268,"pl-c1"],[268,269,"pl-s"],[271,272,"pl-s"],[272,301,"pl-c1"],[301,302,"pl-s"],[304,305,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,24,"pl-c1"],[24,25,"pl-s"],[28,29,"pl-s"],[29,46,"pl-c1"],[46,47,"pl-s"],[48,49,"pl-s"],[49,59,"pl-c1"],[59,60,"pl-s"],[61,62,"pl-ml"],[69,70,"pl-ml"],[96,97,"pl-s"],[97,109,"pl-c1"],[109,110,"pl-s"],[123,124,"pl-s"],[124,130,"pl-c1"],[130,131,"pl-s"],[133,134,"pl-s"],[134,137,"pl-c1"],[137,138,"pl-s"],[176,177,"pl-ml"],[221,222,"pl-ml"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,32,"pl-c1"],[32,33,"pl-s"],[35,36,"pl-s"],[36,62,"pl-c1"],[62,63,"pl-s"],[66,67,"pl-s"],[67,79,"pl-c1"],[79,80,"pl-s"],[101,102,"pl-s"],[102,108,"pl-c1"],[108,109,"pl-s"],[111,112,"pl-ml"],[118,119,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,44,"pl-c1"],[44,45,"pl-s"],[72,73,"pl-s"],[73,79,"pl-c1"],[79,80,"pl-s"],[83,84,"pl-s"],[84,90,"pl-c1"],[90,91,"pl-s"],[98,99,"pl-s"],[99,104,"pl-c1"],[104,105,"pl-s"],[123,124,"pl-ml"],[130,131,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,43,"pl-c1"],[43,44,"pl-s"],[56,57,"pl-s"],[57,60,"pl-c1"],[60,61,"pl-s"],[74,75,"pl-s"],[75,81,"pl-c1"],[81,82,"pl-s"],[89,90,"pl-s"],[90,95,"pl-c1"],[95,96,"pl-s"],[108,109,"pl-s"],[109,116,"pl-c1"],[116,117,"pl-s"],[149,150,"pl-ml"],[156,157,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,47,"pl-c1"],[47,48,"pl-s"],[75,76,"pl-s"],[76,81,"pl-c1"],[81,82,"pl-s"],[89,90,"pl-s"],[90,95,"pl-c1"],[95,96,"pl-s"],[97,98,"pl-ml"],[104,105,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,45,"pl-c1"],[45,46,"pl-s"],[63,64,"pl-s"],[64,74,"pl-c1"],[74,75,"pl-s"],[98,99,"pl-s"],[99,108,"pl-c1"],[108,109,"pl-s"],[116,117,"pl-s"],[117,122,"pl-c1"],[122,123,"pl-s"],[136,137,"pl-ml"],[143,144,"pl-ml"]],[],[[14,15,"pl-s"],[15,52,"pl-c1"],[52,53,"pl-s"],[61,62,"pl-s"],[62,83,"pl-c1"],[83,84,"pl-s"]],[[51,52,"pl-s"],[52,71,"pl-c1"],[71,72,"pl-s"]],[[1,2,"pl-s"],[2,22,"pl-c1"],[22,23,"pl-s"],[39,40,"pl-s"],[40,60,"pl-c1"],[60,61,"pl-s"],[66,67,"pl-s"],[67,77,"pl-c1"],[77,78,"pl-s"]],[],[[0,81,"pl-mh"],[3,81,"pl-en"],[76,80,"pl-s"],[77,80,"pl-corl"]],[],[],[[14,15,"pl-s"],[15,66,"pl-c1"],[66,67,"pl-s"]],[[0,1,"pl-s"],[1,17,"pl-c1"],[17,18,"pl-s"],[42,43,"pl-s"],[43,55,"pl-c1"],[55,56,"pl-s"]],[],[],[],[[53,56,"pl-s"],[54,56,"pl-corl"],[57,61,"pl-s"],[58,61,"pl-corl"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,19,"pl-c1"],[19,20,"pl-s"],[22,23,"pl-s"],[23,30,"pl-c1"],[30,31,"pl-s"],[46,47,"pl-s"],[47,58,"pl-c1"],[58,59,"pl-s"],[62,63,"pl-s"],[63,66,"pl-c1"],[66,67,"pl-s"],[68,69,"pl-s"],[69,74,"pl-c1"],[74,75,"pl-s"],[146,147,"pl-s"],[147,175,"pl-c1"],[175,176,"pl-s"],[187,188,"pl-s"],[188,210,"pl-c1"],[210,211,"pl-s"],[221,222,"pl-ml"],[228,229,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[37,38,"pl-s"],[38,67,"pl-c1"],[67,68,"pl-s"],[70,71,"pl-s"],[71,72,"pl-c1"],[72,73,"pl-s"],[130,131,"pl-s"],[131,136,"pl-c1"],[136,137,"pl-s"],[164,165,"pl-s"],[165,176,"pl-c1"],[176,177,"pl-s"],[201,202,"pl-ml"],[208,209,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[20,21,"pl-s"],[21,25,"pl-c1"],[25,26,"pl-s"],[48,49,"pl-s"],[49,53,"pl-c1"],[53,54,"pl-s"],[91,92,"pl-s"],[92,142,"pl-c1"],[142,143,"pl-s"],[145,146,"pl-s"],[146,155,"pl-c1"],[155,156,"pl-s"],[162,163,"pl-s"],[163,184,"pl-c1"],[184,185,"pl-s"],[191,192,"pl-s"],[192,205,"pl-c1"],[205,206,"pl-s"],[328,329,"pl-ml"],[335,336,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,24,"pl-c1"],[24,25,"pl-s"],[68,69,"pl-s"],[69,86,"pl-c1"],[86,87,"pl-s"],[153,154,"pl-s"],[154,168,"pl-c1"],[168,169,"pl-s"],[200,201,"pl-s"],[201,227,"pl-c1"],[227,228,"pl-s"],[297,298,"pl-ml"],[304,305,"pl-ml"]],[],[[14,15,"pl-s"],[15,52,"pl-c1"],[52,53,"pl-s"],[61,62,"pl-s"],[62,83,"pl-c1"],[83,84,"pl-s"]],[[40,41,"pl-s"],[41,72,"pl-c1"],[72,73,"pl-s"]],[[0,1,"pl-s"],[1,11,"pl-c1"],[11,12,"pl-s"],[48,51,"pl-s"],[49,51,"pl-corl"],[52,56,"pl-s"],[53,56,"pl-corl"]],[],[[0,55,"pl-mh"],[3,55,"pl-en"],[50,54,"pl-s"],[51,54,"pl-corl"]],[],[[65,69,"pl-s"],[66,69,"pl-corl"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[21,23,"pl-s"],[31,33,"pl-s"],[53,54,"pl-s"],[54,74,"pl-c1"],[74,75,"pl-s"],[139,140,"pl-ml"],[146,147,"pl-ml"]],[[0,1,"pl-ml"],[9,10,"pl-s"],[10,21,"pl-c1"],[21,22,"pl-s"],[197,198,"pl-ml"],[209,217,"pl-s"],[209,213,"pl-corl"],[214,217,"pl-corl"],[219,234,"pl-s"],[219,232,"pl-corl"],[233,234,"pl-corl"],[236,247,"pl-s"],[236,245,"pl-corl"],[246,247,"pl-corl"],[249,257,"pl-s"],[249,255,"pl-corl"],[256,257,"pl-corl"],[258,259,"pl-ml"]],[[0,1,"pl-ml"],[33,34,"pl-s"],[34,51,"pl-c1"],[51,52,"pl-s"],[103,104,"pl-ml"],[110,111,"pl-ml"]],[[0,1,"pl-ml"],[106,107,"pl-ml"],[113,114,"pl-ml"]],[[0,1,"pl-ml"],[49,53,"pl-s"],[50,53,"pl-corl"],[112,113,"pl-ml"],[119,120,"pl-ml"]],[],[[0,89,"pl-mh"],[3,89,"pl-en"],[75,78,"pl-s"],[76,78,"pl-corl"],[85,88,"pl-s"],[86,88,"pl-corl"]],[],[],[],[[0,1,"pl-s"],[1,24,"pl-c1"],[24,25,"pl-s"]],[[0,1,"pl-s"],[1,54,"pl-c1"],[54,55,"pl-s"],[57,58,"pl-s"],[58,69,"pl-c1"],[69,70,"pl-s"],[72,73,"pl-s"],[73,83,"pl-c1"],[83,84,"pl-s"]],[],[[20,21,"pl-s"],[21,25,"pl-c1"],[25,26,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[49,50,"pl-s"],[50,56,"pl-c1"],[56,57,"pl-s"],[58,59,"pl-s"],[59,69,"pl-c1"],[69,70,"pl-s"],[76,77,"pl-s"],[77,86,"pl-c1"],[86,87,"pl-s"],[111,112,"pl-s"],[112,119,"pl-c1"],[119,120,"pl-s"],[121,122,"pl-s"],[122,126,"pl-c1"],[126,127,"pl-s"],[128,129,"pl-s"],[129,137,"pl-c1"],[137,138,"pl-s"],[174,175,"pl-s"],[175,182,"pl-c1"],[182,183,"pl-s"],[185,186,"pl-s"],[186,199,"pl-c1"],[199,200,"pl-s"],[201,202,"pl-ml"],[208,209,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[17,18,"pl-s"],[18,23,"pl-c1"],[23,24,"pl-s"],[64,65,"pl-s"],[65,69,"pl-c1"],[69,70,"pl-s"],[94,95,"pl-s"],[95,107,"pl-c1"],[107,108,"pl-s"],[109,110,"pl-s"],[110,122,"pl-c1"],[122,123,"pl-s"],[144,145,"pl-s"],[145,179,"pl-c1"],[179,180,"pl-s"],[203,204,"pl-s"],[204,225,"pl-c1"],[225,226,"pl-s"],[227,228,"pl-ml"],[234,235,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[242,243,"pl-ml"],[249,250,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,49,"pl-c1"],[49,50,"pl-s"],[64,65,"pl-s"],[65,80,"pl-c1"],[80,81,"pl-s"],[121,122,"pl-ml"],[128,129,"pl-ml"]],[[0,1,"pl-ml"],[222,223,"pl-s"],[223,236,"pl-c1"],[236,237,"pl-s"],[238,239,"pl-ml"],[258,259,"pl-s"],[259,264,"pl-c1"],[264,265,"pl-s"],[274,275,"pl-ml"]],[[0,1,"pl-ml"],[23,26,"pl-s"],[24,26,"pl-corl"],[39,40,"pl-ml"],[46,47,"pl-ml"]],[],[],[[0,1,"pl-s"],[1,17,"pl-c1"],[17,18,"pl-s"],[20,21,"pl-s"],[21,37,"pl-c1"],[37,38,"pl-s"]],[[11,12,"pl-s"],[12,41,"pl-c1"],[41,42,"pl-s"],[50,51,"pl-s"],[51,82,"pl-c1"],[82,83,"pl-s"]],[[40,43,"pl-s"],[41,43,"pl-corl"],[54,55,"pl-s"],[55,67,"pl-c1"],[67,68,"pl-s"]],[],[[14,15,"pl-s"],[15,29,"pl-c1"],[29,30,"pl-s"],[38,39,"pl-s"],[39,82,"pl-c1"],[82,83,"pl-s"]],[[7,8,"pl-s"],[8,58,"pl-c1"],[58,59,"pl-s"]],[[0,1,"pl-s"],[1,27,"pl-c1"],[27,28,"pl-s"],[33,34,"pl-s"],[34,44,"pl-c1"],[44,45,"pl-s"]],[],[[0,75,"pl-mh"],[3,75,"pl-en"],[71,74,"pl-s"],[72,74,"pl-corl"]],[],[],[],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[85,88,"pl-s"],[86,88,"pl-corl"],[96,97,"pl-s"],[97,104,"pl-c1"],[104,105,"pl-s"],[111,112,"pl-s"],[112,119,"pl-c1"],[119,120,"pl-s"],[127,128,"pl-s"],[128,135,"pl-c1"],[135,136,"pl-s"],[137,138,"pl-s"],[138,145,"pl-c1"],[145,146,"pl-s"],[152,153,"pl-s"],[153,160,"pl-c1"],[160,161,"pl-s"],[171,172,"pl-s"],[172,179,"pl-c1"],[179,180,"pl-s"],[215,216,"pl-s"],[216,238,"pl-c1"],[238,239,"pl-s"],[256,257,"pl-ml"],[263,264,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,34,"pl-c1"],[34,35,"pl-s"],[159,160,"pl-s"],[160,164,"pl-c1"],[164,165,"pl-s"],[172,173,"pl-ml"],[179,180,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,31,"pl-c1"],[31,32,"pl-s"],[45,46,"pl-s"],[46,51,"pl-c1"],[51,52,"pl-s"],[55,56,"pl-s"],[56,69,"pl-c1"],[69,70,"pl-s"],[79,80,"pl-s"],[80,94,"pl-c1"],[94,95,"pl-s"],[103,104,"pl-s"],[104,115,"pl-c1"],[115,116,"pl-s"],[119,120,"pl-s"],[120,128,"pl-c1"],[128,129,"pl-s"],[206,207,"pl-ml"],[213,214,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[30,31,"pl-s"],[31,48,"pl-c1"],[48,49,"pl-s"],[56,57,"pl-s"],[57,76,"pl-c1"],[76,77,"pl-s"],[99,100,"pl-s"],[100,109,"pl-c1"],[109,110,"pl-s"],[113,114,"pl-s"],[114,124,"pl-c1"],[124,125,"pl-s"],[169,170,"pl-ml"],[176,177,"pl-ml"]],[[0,1,"pl-ml"],[14,15,"pl-s"],[15,69,"pl-c1"],[69,70,"pl-s"],[71,72,"pl-ml"],[80,81,"pl-ml"]],[[0,1,"pl-ml"],[92,93,"pl-s"],[93,98,"pl-c1"],[98,99,"pl-s"],[108,109,"pl-ml"],[115,116,"pl-ml"]],[],[[14,15,"pl-s"],[15,27,"pl-c1"],[27,28,"pl-s"],[36,37,"pl-s"],[37,69,"pl-c1"],[69,70,"pl-s"]],[[7,8,"pl-s"],[8,38,"pl-c1"],[38,39,"pl-s"],[44,45,"pl-s"],[45,55,"pl-c1"],[55,56,"pl-s"]],[[25,26,"pl-s"],[26,39,"pl-c1"],[39,40,"pl-s"]],[],[],[[0,69,"pl-mh"],[3,69,"pl-en"],[55,58,"pl-s"],[56,58,"pl-corl"],[65,68,"pl-s"],[66,68,"pl-corl"]],[],[[6,7,"pl-s"],[7,26,"pl-c1"],[26,27,"pl-s"],[52,53,"pl-s"],[53,66,"pl-c1"],[66,67,"pl-s"]],[[48,49,"pl-s"],[49,59,"pl-c1"],[59,60,"pl-s"]],[[22,23,"pl-s"],[23,67,"pl-c1"],[67,68,"pl-s"]],[[0,1,"pl-s"],[1,11,"pl-c1"],[11,12,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[28,29,"pl-s"],[29,35,"pl-c1"],[35,36,"pl-s"],[39,40,"pl-s"],[40,68,"pl-c1"],[68,69,"pl-s"],[99,102,"pl-s"],[100,102,"pl-corl"],[183,184,"pl-ml"],[190,191,"pl-ml"]],[[0,1,"pl-ml"],[43,44,"pl-s"],[44,50,"pl-c1"],[50,51,"pl-s"],[53,54,"pl-s"],[54,58,"pl-c1"],[58,59,"pl-s"],[61,62,"pl-s"],[62,64,"pl-c1"],[64,65,"pl-s"],[67,68,"pl-s"],[68,75,"pl-c1"],[75,76,"pl-s"],[78,79,"pl-s"],[79,87,"pl-c1"],[87,88,"pl-s"],[126,127,"pl-s"],[127,144,"pl-c1"],[144,145,"pl-s"],[227,228,"pl-ml"],[234,235,"pl-ml"]],[[0,1,"pl-ml"],[119,120,"pl-ml"],[126,127,"pl-ml"]],[[0,1,"pl-ml"],[101,102,"pl-s"],[102,107,"pl-c1"],[107,108,"pl-s"],[117,118,"pl-ml"],[124,125,"pl-ml"]],[],[[14,15,"pl-s"],[15,46,"pl-c1"],[46,47,"pl-s"]],[],[],[[14,15,"pl-s"],[15,44,"pl-c1"],[44,45,"pl-s"],[48,49,"pl-s"],[49,59,"pl-c1"],[59,60,"pl-s"]],[[26,27,"pl-s"],[27,35,"pl-c1"],[35,36,"pl-s"]],[],[[32,33,"pl-s"],[33,50,"pl-c1"],[50,51,"pl-s"]],[],[[0,93,"pl-mh"],[3,93,"pl-en"],[79,82,"pl-s"],[80,82,"pl-corl"],[89,92,"pl-s"],[90,92,"pl-corl"]],[],[[16,17,"pl-s"],[17,37,"pl-c1"],[37,38,"pl-s"]],[[17,18,"pl-s"],[18,33,"pl-c1"],[33,34,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[16,17,"pl-s"],[17,23,"pl-c1"],[23,24,"pl-s"],[58,59,"pl-s"],[59,62,"pl-c1"],[62,63,"pl-s"],[99,100,"pl-s"],[100,105,"pl-c1"],[105,106,"pl-s"],[108,109,"pl-s"],[109,127,"pl-c1"],[127,128,"pl-s"],[131,132,"pl-s"],[132,148,"pl-c1"],[148,149,"pl-s"],[195,196,"pl-s"],[196,205,"pl-c1"],[205,206,"pl-s"],[231,232,"pl-ml"],[238,239,"pl-ml"]],[[0,1,"pl-ml"],[40,41,"pl-s"],[41,55,"pl-c1"],[55,56,"pl-s"],[79,80,"pl-s"],[80,95,"pl-c1"],[95,96,"pl-s"],[167,168,"pl-ml"],[174,175,"pl-ml"]],[[0,1,"pl-ml"],[46,47,"pl-s"],[47,53,"pl-c1"],[53,54,"pl-s"],[55,56,"pl-s"],[56,59,"pl-c1"],[59,60,"pl-s"],[61,62,"pl-s"],[62,67,"pl-c1"],[67,68,"pl-s"],[83,84,"pl-s"],[84,99,"pl-c1"],[99,100,"pl-s"],[104,105,"pl-s"],[105,132,"pl-c1"],[132,133,"pl-s"],[134,135,"pl-ml"],[141,142,"pl-ml"]],[],[[14,15,"pl-s"],[15,68,"pl-c1"],[68,69,"pl-s"]],[[0,1,"pl-s"],[1,55,"pl-c1"],[55,56,"pl-s"]],[[1,2,"pl-s"],[2,37,"pl-c1"],[37,38,"pl-s"],[40,41,"pl-s"],[41,86,"pl-c1"],[86,87,"pl-s"]],[[7,10,"pl-s"],[8,10,"pl-corl"],[24,25,"pl-s"],[25,28,"pl-c1"],[28,29,"pl-s"],[37,38,"pl-s"],[38,49,"pl-c1"],[49,50,"pl-s"]],[[61,62,"pl-s"],[62,82,"pl-c1"],[82,83,"pl-s"]],[],[[0,75,"pl-mh"],[3,75,"pl-en"],[70,74,"pl-s"],[71,74,"pl-corl"]],[],[[31,33,"pl-s"],[45,47,"pl-s"],[68,69,"pl-s"],[69,73,"pl-c1"],[73,74,"pl-s"]],[[30,31,"pl-s"],[31,59,"pl-c1"],[59,60,"pl-s"]],[[0,1,"pl-s"],[1,13,"pl-c1"],[13,14,"pl-s"],[16,17,"pl-s"],[17,41,"pl-c1"],[41,42,"pl-s"]],[[0,1,"pl-s"],[1,10,"pl-c1"],[10,11,"pl-s"]],[[0,1,"pl-s"],[1,21,"pl-c1"],[21,22,"pl-s"]],[[29,31,"pl-s"],[32,34,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[41,42,"pl-s"],[42,53,"pl-c1"],[53,54,"pl-s"],[71,72,"pl-s"],[72,77,"pl-c1"],[77,78,"pl-s"],[91,92,"pl-s"],[92,107,"pl-c1"],[107,108,"pl-s"],[143,144,"pl-s"],[144,156,"pl-c1"],[156,157,"pl-s"],[158,159,"pl-s"],[159,176,"pl-c1"],[176,177,"pl-s"],[207,208,"pl-ml"],[214,215,"pl-ml"]],[[0,1,"pl-ml"],[13,14,"pl-s"],[14,29,"pl-c1"],[29,30,"pl-s"],[32,33,"pl-s"],[33,52,"pl-c1"],[52,53,"pl-s"],[55,56,"pl-s"],[56,93,"pl-c1"],[93,94,"pl-s"],[96,97,"pl-s"],[97,134,"pl-c1"],[134,135,"pl-s"],[138,139,"pl-s"],[139,170,"pl-c1"],[170,171,"pl-s"],[178,179,"pl-ml"],[185,186,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,23,"pl-c1"],[23,24,"pl-s"],[30,31,"pl-s"],[31,40,"pl-c1"],[40,41,"pl-s"],[43,44,"pl-s"],[44,48,"pl-c1"],[48,49,"pl-s"],[52,53,"pl-s"],[53,83,"pl-c1"],[83,84,"pl-s"],[107,108,"pl-ml"],[114,115,"pl-ml"]],[[0,1,"pl-ml"],[64,65,"pl-s"],[65,72,"pl-c1"],[72,73,"pl-s"],[224,225,"pl-ml"],[231,232,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-s"],[9,29,"pl-c1"],[29,30,"pl-s"],[57,58,"pl-s"],[58,79,"pl-c1"],[79,80,"pl-s"],[92,93,"pl-ml"],[99,100,"pl-ml"]],[],[[14,15,"pl-s"],[15,52,"pl-c1"],[52,53,"pl-s"]],[[0,1,"pl-s"],[1,92,"pl-c1"],[92,93,"pl-s"]],[[14,15,"pl-s"],[15,26,"pl-c1"],[26,27,"pl-s"]],[[28,29,"pl-s"],[29,49,"pl-c1"],[49,50,"pl-s"],[59,60,"pl-s"],[60,81,"pl-c1"],[81,82,"pl-s"]],[],[[0,65,"pl-mh"],[3,65,"pl-en"],[61,64,"pl-s"],[62,64,"pl-corl"]],[],[],[[29,32,"pl-s"],[30,32,"pl-corl"],[33,36,"pl-s"],[34,36,"pl-corl"]],[[1,4,"pl-s"],[2,4,"pl-corl"]],[],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[154,155,"pl-s"],[155,175,"pl-c1"],[175,176,"pl-s"],[177,178,"pl-ml"],[184,185,"pl-ml"]],[[0,1,"pl-ml"],[47,48,"pl-s"],[48,65,"pl-c1"],[65,66,"pl-s"],[92,93,"pl-s"],[93,102,"pl-c1"],[102,103,"pl-s"],[105,106,"pl-s"],[106,118,"pl-c1"],[118,119,"pl-s"],[121,122,"pl-s"],[122,133,"pl-c1"],[133,134,"pl-s"],[136,137,"pl-s"],[137,146,"pl-c1"],[146,147,"pl-s"],[149,150,"pl-s"],[150,174,"pl-c1"],[174,175,"pl-s"],[193,194,"pl-ml"],[200,201,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,21,"pl-c1"],[21,22,"pl-s"],[24,25,"pl-s"],[25,40,"pl-c1"],[40,41,"pl-s"],[71,72,"pl-s"],[72,98,"pl-c1"],[98,99,"pl-s"],[160,161,"pl-s"],[161,180,"pl-c1"],[180,181,"pl-s"],[212,213,"pl-s"],[213,247,"pl-c1"],[247,248,"pl-s"],[257,258,"pl-ml"],[264,265,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,15,"pl-c1"],[15,16,"pl-s"],[154,155,"pl-ml"],[161,162,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-s"],[9,29,"pl-c1"],[29,30,"pl-s"],[97,98,"pl-ml"],[104,105,"pl-ml"]],[],[[14,15,"pl-s"],[15,54,"pl-c1"],[54,55,"pl-s"]],[],[[62,65,"pl-s"],[63,65,"pl-corl"]],[[41,42,"pl-s"]],[[7,8,"pl-s"],[8,31,"pl-c1"],[31,32,"pl-s"]],[],[[0,74,"pl-mh"],[3,74,"pl-en"],[70,73,"pl-s"],[71,73,"pl-corl"]],[],[],[],[[60,63,"pl-s"],[61,63,"pl-corl"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[28,31,"pl-s"],[29,31,"pl-corl"],[32,35,"pl-s"],[33,35,"pl-corl"],[149,152,"pl-s"],[150,152,"pl-corl"],[172,173,"pl-ml"],[179,180,"pl-ml"]],[[0,1,"pl-ml"],[10,11,"pl-s"],[11,21,"pl-c1"],[21,22,"pl-s"],[46,47,"pl-s"],[47,58,"pl-c1"],[58,59,"pl-s"],[83,84,"pl-ml"],[90,91,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[71,72,"pl-ml"],[78,79,"pl-ml"]],[[0,1,"pl-ml"],[15,16,"pl-s"],[16,25,"pl-c1"],[25,26,"pl-s"],[72,73,"pl-s"],[73,79,"pl-c1"],[79,80,"pl-s"],[138,139,"pl-ml"],[145,146,"pl-ml"]],[],[[14,15,"pl-s"],[15,51,"pl-c1"],[51,52,"pl-s"]],[[0,1,"pl-s"],[1,87,"pl-c1"],[87,88,"pl-s"]],[[2,3,"pl-s"],[3,91,"pl-c1"],[91,92,"pl-s"]],[[10,11,"pl-s"],[11,36,"pl-c1"],[36,37,"pl-s"]],[[13,14,"pl-s"],[14,20,"pl-c1"],[20,21,"pl-s"],[24,25,"pl-s"],[25,42,"pl-c1"],[42,43,"pl-s"]],[[1,2,"pl-s"],[2,60,"pl-c1"],[60,61,"pl-s"]],[],[[1,4,"pl-s"],[2,4,"pl-corl"],[22,23,"pl-s"],[23,42,"pl-c1"],[42,43,"pl-s"]],[],[[0,72,"pl-mh"],[3,72,"pl-en"],[67,71,"pl-s"],[68,71,"pl-corl"]],[],[],[[60,61,"pl-s"]],[[8,9,"pl-s"]],[[72,76,"pl-s"],[73,76,"pl-corl"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,16,"pl-c1"],[16,17,"pl-s"],[60,61,"pl-ml"],[67,68,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"],[56,57,"pl-s"],[57,68,"pl-c1"],[68,69,"pl-s"],[127,128,"pl-ml"],[134,135,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,11,"pl-c1"],[11,12,"pl-s"],[47,48,"pl-s"],[48,50,"pl-c1"],[50,51,"pl-s"],[115,116,"pl-ml"],[122,123,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,22,"pl-c1"],[22,23,"pl-s"],[101,102,"pl-ml"],[110,111,"pl-s"],[111,161,"pl-c1"],[161,162,"pl-s"],[240,241,"pl-ml"]],[[0,1,"pl-ml"],[24,25,"pl-s"],[25,53,"pl-c1"],[53,54,"pl-s"],[80,81,"pl-ml"],[90,91,"pl-ml"]],[[0,1,"pl-ml"],[15,16,"pl-ml"],[22,23,"pl-ml"]],[],[],[[17,18,"pl-s"],[18,23,"pl-c1"],[23,24,"pl-s"]],[[8,9,"pl-s"],[9,22,"pl-c1"],[22,23,"pl-s"]],[[34,35,"pl-s"],[35,59,"pl-c1"],[59,60,"pl-s"]],[],[[0,53,"pl-mh"],[3,53,"pl-en"],[48,52,"pl-s"],[49,52,"pl-corl"]],[],[[25,26,"pl-s"],[26,41,"pl-c1"],[41,42,"pl-s"],[52,53,"pl-s"],[53,61,"pl-c1"],[61,62,"pl-s"],[64,68,"pl-s"],[65,68,"pl-corl"]],[[17,18,"pl-s"],[18,28,"pl-c1"],[28,29,"pl-s"],[42,43,"pl-s"],[43,56,"pl-c1"],[56,57,"pl-s"]],[],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,32,"pl-c1"],[32,33,"pl-s"],[35,36,"pl-s"],[36,45,"pl-c1"],[45,46,"pl-s"],[49,50,"pl-s"],[50,63,"pl-c1"],[63,64,"pl-s"],[96,97,"pl-s"],[97,106,"pl-c1"],[106,107,"pl-s"],[119,120,"pl-ml"],[126,127,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,33,"pl-c1"],[33,34,"pl-s"],[37,38,"pl-s"],[38,59,"pl-c1"],[59,60,"pl-s"],[61,62,"pl-ml"],[68,69,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[27,28,"pl-s"],[28,38,"pl-c1"],[38,39,"pl-s"],[60,61,"pl-s"],[61,72,"pl-c1"],[72,73,"pl-s"],[106,107,"pl-s"],[107,118,"pl-c1"],[118,119,"pl-s"],[120,121,"pl-ml"],[127,128,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-s"],[8,27,"pl-c1"],[27,28,"pl-s"],[77,78,"pl-s"],[78,90,"pl-c1"],[90,91,"pl-s"],[123,124,"pl-ml"],[132,133,"pl-s"],[133,207,"pl-c1"],[207,208,"pl-s"],[215,216,"pl-ml"]],[[0,1,"pl-ml"],[16,17,"pl-s"],[17,44,"pl-c1"],[44,45,"pl-s"],[69,70,"pl-ml"],[174,175,"pl-ml"]],[[0,1,"pl-ml"],[26,27,"pl-s"],[27,45,"pl-c1"],[45,46,"pl-s"],[48,49,"pl-ml"],[65,66,"pl-ml"]],[[0,1,"pl-ml"],[49,50,"pl-ml"],[68,69,"pl-ml"]],[[0,1,"pl-ml"],[28,29,"pl-s"],[29,35,"pl-c1"],[35,36,"pl-s"],[46,47,"pl-s"],[47,58,"pl-c1"],[58,59,"pl-s"],[60,61,"pl-ml"],[67,68,"pl-ml"]],[[0,1,"pl-ml"],[58,62,"pl-s"],[59,62,"pl-corl"],[65,66,"pl-s"],[85,86,"pl-ml"],[125,126,"pl-ml"]],[],[[7,8,"pl-s"],[8,34,"pl-c1"],[34,35,"pl-s"],[40,44,"pl-s"],[41,44,"pl-corl"]],[],[[0,77,"pl-mh"],[3,77,"pl-en"],[72,76,"pl-s"],[73,76,"pl-corl"]],[],[[22,25,"pl-s"],[23,25,"pl-corl"]],[[0,1,"pl-s"],[1,11,"pl-c1"],[11,12,"pl-s"],[21,22,"pl-s"],[22,26,"pl-c1"],[26,27,"pl-s"],[43,44,"pl-s"],[44,60,"pl-c1"],[60,61,"pl-s"],[74,75,"pl-s"],[75,79,"pl-c1"],[79,80,"pl-s"]],[[43,44,"pl-s"],[44,53,"pl-c1"],[53,54,"pl-s"]],[[0,1,"pl-s"],[1,13,"pl-c1"],[13,14,"pl-s"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[39,40,"pl-s"],[40,65,"pl-c1"],[65,66,"pl-s"],[129,130,"pl-s"],[130,146,"pl-c1"],[146,147,"pl-s"],[162,163,"pl-s"],[163,174,"pl-c1"],[174,175,"pl-s"],[196,197,"pl-ml"],[233,234,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[36,37,"pl-s"],[37,47,"pl-c1"],[47,48,"pl-s"],[50,53,"pl-s"],[51,53,"pl-corl"],[65,66,"pl-s"],[66,76,"pl-c1"],[76,77,"pl-s"],[100,101,"pl-s"],[101,120,"pl-c1"],[120,121,"pl-s"],[133,136,"pl-s"],[134,136,"pl-corl"],[143,144,"pl-s"],[144,163,"pl-c1"],[163,164,"pl-s"],[282,283,"pl-s"],[283,331,"pl-c1"],[331,332,"pl-s"],[334,335,"pl-s"],[335,344,"pl-c1"],[344,345,"pl-s"],[346,347,"pl-s"],[347,354,"pl-c1"],[354,355,"pl-s"],[364,365,"pl-s"],[365,381,"pl-c1"],[381,382,"pl-s"],[385,386,"pl-s"],[386,395,"pl-c1"],[395,396,"pl-s"],[402,403,"pl-ml"],[409,410,"pl-ml"]],[[0,1,"pl-ml"],[15,16,"pl-s"],[16,36,"pl-c1"],[36,37,"pl-s"],[169,170,"pl-ml"],[215,216,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[67,68,"pl-s"],[104,105,"pl-s"],[105,123,"pl-c1"],[123,124,"pl-s"],[126,127,"pl-ml"],[133,134,"pl-ml"]],[[0,1,"pl-ml"],[10,11,"pl-s"],[11,21,"pl-c1"],[21,22,"pl-s"],[32,33,"pl-s"],[33,37,"pl-c1"],[37,38,"pl-s"],[41,42,"pl-s"],[42,62,"pl-c1"],[62,63,"pl-s"],[149,150,"pl-ml"],[156,157,"pl-ml"]],[[0,1,"pl-ml"],[294,295,"pl-ml"],[303,304,"pl-s"],[304,376,"pl-c1"],[376,377,"pl-s"],[384,385,"pl-ml"]],[[0,1,"pl-ml"],[115,116,"pl-s"],[116,127,"pl-c1"],[127,128,"pl-s"],[260,263,"pl-s"],[261,263,"pl-corl"],[265,266,"pl-ml"],[272,273,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-s"],[9,29,"pl-c1"],[29,30,"pl-s"],[101,102,"pl-ml"],[108,109,"pl-ml"]],[],[[14,15,"pl-s"],[15,29,"pl-c1"],[29,30,"pl-s"],[122,123,"pl-s"],[123,135,"pl-c1"],[135,136,"pl-s"],[138,139,"pl-s"],[139,158,"pl-c1"],[158,159,"pl-s"],[215,216,"pl-s"],[216,273,"pl-c1"],[273,274,"pl-s"],[283,284,"pl-s"],[284,302,"pl-c1"],[302,303,"pl-s"],[324,325,"pl-s"],[325,344,"pl-c1"],[344,345,"pl-s"]],[],[[0,77,"pl-mh"],[3,77,"pl-en"],[61,65,"pl-s"],[62,65,"pl-corl"],[72,76,"pl-s"],[73,76,"pl-corl"]],[],[[27,29,"pl-s"],[39,41,"pl-s"]],[],[[7,11,"pl-s"],[8,11,"pl-corl"],[33,37,"pl-s"],[34,37,"pl-corl"]],[[22,26,"pl-s"],[23,26,"pl-corl"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,13,"pl-c1"],[13,14,"pl-s"],[16,17,"pl-s"],[17,21,"pl-c1"],[21,22,"pl-s"],[23,24,"pl-s"],[24,33,"pl-c1"],[33,34,"pl-s"],[132,133,"pl-s"],[133,134,"pl-s"],[163,164,"pl-ml"],[170,171,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[133,134,"pl-s"],[134,140,"pl-c1"],[140,141,"pl-s"],[142,143,"pl-s"],[143,150,"pl-c1"],[150,151,"pl-s"],[208,209,"pl-s"],[209,216,"pl-c1"],[216,217,"pl-s"],[296,297,"pl-s"],[297,311,"pl-c1"],[311,312,"pl-s"],[399,400,"pl-s"],[400,410,"pl-c1"],[410,411,"pl-s"],[439,440,"pl-s"],[440,448,"pl-c1"],[448,449,"pl-s"],[452,453,"pl-s"],[453,471,"pl-c1"],[471,472,"pl-s"],[474,475,"pl-s"],[475,487,"pl-c1"],[487,488,"pl-s"],[534,535,"pl-ml"],[541,542,"pl-ml"]],[[0,1,"pl-ml"],[10,11,"pl-s"],[11,32,"pl-c1"],[32,33,"pl-s"],[35,36,"pl-s"],[36,50,"pl-c1"],[50,51,"pl-s"],[106,107,"pl-s"],[107,113,"pl-c1"],[113,114,"pl-s"],[121,122,"pl-s"],[122,137,"pl-c1"],[137,138,"pl-s"],[140,141,"pl-ml"],[147,148,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[55,56,"pl-s"],[56,65,"pl-c1"],[65,66,"pl-s"],[92,93,"pl-s"],[93,101,"pl-c1"],[101,102,"pl-s"],[104,105,"pl-s"],[105,109,"pl-c1"],[109,110,"pl-s"],[112,113,"pl-s"],[113,128,"pl-c1"],[128,129,"pl-s"],[219,220,"pl-ml"],[226,227,"pl-ml"]],[[0,1,"pl-ml"],[72,73,"pl-s"],[73,92,"pl-c1"],[92,93,"pl-s"],[238,239,"pl-ml"],[255,256,"pl-ml"]],[[0,1,"pl-ml"],[103,104,"pl-s"],[104,119,"pl-c1"],[119,120,"pl-s"],[163,164,"pl-ml"],[172,173,"pl-s"],[173,188,"pl-c1"],[188,189,"pl-s"],[253,254,"pl-ml"]],[[0,1,"pl-ml"],[32,33,"pl-s"],[33,44,"pl-c1"],[44,45,"pl-s"],[53,54,"pl-s"],[54,77,"pl-c1"],[77,78,"pl-s"],[116,117,"pl-ml"],[301,302,"pl-ml"]],[[0,1,"pl-ml"],[33,34,"pl-s"],[34,49,"pl-c1"],[49,50,"pl-s"],[66,67,"pl-ml"],[73,74,"pl-ml"]],[[0,1,"pl-ml"],[88,121,"pl-corl"],[133,134,"pl-s"],[134,153,"pl-c1"],[153,154,"pl-s"],[190,191,"pl-ml"],[197,198,"pl-ml"]],[],[[14,15,"pl-s"],[15,36,"pl-c1"],[36,37,"pl-s"],[40,41,"pl-s"],[41,127,"pl-c1"],[127,128,"pl-s"],[140,141,"pl-s"],[141,191,"pl-c1"],[191,192,"pl-s"],[248,249,"pl-s"],[249,281,"pl-c1"],[281,282,"pl-s"],[287,288,"pl-s"],[288,292,"pl-c1"],[292,293,"pl-s"]],[],[[0,105,"pl-mh"],[3,105,"pl-en"],[89,93,"pl-s"],[90,93,"pl-corl"],[100,104,"pl-s"],[101,104,"pl-corl"]],[],[[57,61,"pl-s"],[58,61,"pl-corl"]],[[0,1,"pl-s"],[1,27,"pl-c1"],[27,28,"pl-s"],[50,51,"pl-s"],[51,64,"pl-c1"],[64,65,"pl-s"]],[],[],[[35,36,"pl-s"],[36,60,"pl-c1"],[60,61,"pl-s"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,32,"pl-c1"],[32,33,"pl-s"],[46,47,"pl-s"],[47,76,"pl-c1"],[76,77,"pl-s"],[182,183,"pl-s"],[183,195,"pl-c1"],[195,196,"pl-s"],[447,448,"pl-s"],[448,457,"pl-c1"],[457,458,"pl-s"],[459,460,"pl-s"],[460,467,"pl-c1"],[467,468,"pl-s"],[469,470,"pl-s"],[470,477,"pl-c1"],[477,478,"pl-s"],[592,593,"pl-ml"],[599,600,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,19,"pl-c1"],[19,20,"pl-s"],[27,28,"pl-s"],[28,37,"pl-c1"],[37,38,"pl-s"],[64,68,"pl-s"],[65,68,"pl-corl"],[84,85,"pl-ml"],[91,92,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[17,18,"pl-s"],[18,36,"pl-c1"],[36,37,"pl-s"],[40,41,"pl-s"],[41,54,"pl-c1"],[54,55,"pl-s"],[102,103,"pl-ml"],[109,110,"pl-ml"]],[[0,1,"pl-ml"],[151,152,"pl-ml"],[168,169,"pl-ml"]],[[0,1,"pl-ml"],[268,269,"pl-ml"],[287,291,"pl-s"],[288,291,"pl-corl"],[293,294,"pl-ml"]],[[0,1,"pl-ml"],[101,102,"pl-ml"],[108,109,"pl-ml"]],[],[[14,15,"pl-s"],[15,63,"pl-c1"],[63,64,"pl-s"],[67,68,"pl-s"],[68,80,"pl-c1"],[80,81,"pl-s"]],[[11,12,"pl-s"],[12,78,"pl-c1"],[78,79,"pl-s"]],[[14,15,"pl-s"],[15,44,"pl-c1"],[44,45,"pl-s"],[50,51,"pl-s"],[51,55,"pl-c1"],[55,56,"pl-s"]],[],[[0,95,"pl-mh"],[3,95,"pl-en"],[79,83,"pl-s"],[80,83,"pl-corl"],[90,94,"pl-s"],[91,94,"pl-corl"]],[],[],[],[[0,1,"pl-s"],[1,22,"pl-c1"],[22,23,"pl-s"]],[[23,27,"pl-s"],[24,27,"pl-corl"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,31,"pl-c1"],[31,32,"pl-s"],[38,39,"pl-s"],[39,55,"pl-c1"],[55,56,"pl-s"],[95,99,"pl-s"],[96,99,"pl-corl"],[167,168,"pl-ml"],[174,175,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[34,35,"pl-s"],[35,48,"pl-c1"],[48,49,"pl-s"],[52,53,"pl-s"],[53,62,"pl-c1"],[62,63,"pl-s"],[210,211,"pl-s"],[211,238,"pl-c1"],[238,239,"pl-s"],[248,249,"pl-s"],[249,371,"pl-c1"],[371,372,"pl-s"],[376,377,"pl-s"],[377,380,"pl-c1"],[380,381,"pl-s"],[577,578,"pl-ml"],[584,585,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,39,"pl-c1"],[39,40,"pl-s"],[100,101,"pl-ml"],[103,104,"pl-s"],[104,119,"pl-c1"],[119,120,"pl-s"],[129,130,"pl-s"],[130,131,"pl-c1"],[131,132,"pl-s"],[139,140,"pl-s"],[140,163,"pl-c1"],[163,164,"pl-s"],[173,174,"pl-s"],[174,176,"pl-c1"],[176,177,"pl-s"],[256,257,"pl-ml"],[263,264,"pl-ml"]],[[0,1,"pl-ml"],[41,63,"pl-corl"],[239,243,"pl-s"],[240,243,"pl-corl"],[267,268,"pl-ml"],[313,314,"pl-ml"]],[[0,1,"pl-ml"],[168,169,"pl-ml"],[185,186,"pl-ml"]],[[0,1,"pl-ml"],[25,26,"pl-s"],[26,48,"pl-c1"],[48,49,"pl-s"],[328,329,"pl-ml"],[368,369,"pl-ml"]],[[0,1,"pl-ml"],[106,107,"pl-ml"],[113,114,"pl-ml"]],[],[[14,15,"pl-s"],[15,84,"pl-c1"],[84,85,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[36,37,"pl-s"],[37,83,"pl-c1"],[83,84,"pl-s"]],[],[[7,8,"pl-s"],[8,33,"pl-c1"],[33,34,"pl-s"],[39,40,"pl-s"],[40,44,"pl-c1"],[44,45,"pl-s"]],[],[[0,110,"pl-mh"],[3,110,"pl-en"],[94,98,"pl-s"],[95,98,"pl-corl"],[105,109,"pl-s"],[106,109,"pl-corl"]],[],[[30,34,"pl-s"],[31,34,"pl-corl"]],[],[[69,73,"pl-s"],[70,73,"pl-corl"]],[[16,19,"pl-s"],[17,19,"pl-corl"],[21,23,"pl-s"],[32,34,"pl-s"],[48,49,"pl-s"],[49,70,"pl-c1"],[70,71,"pl-s"]],[[41,45,"pl-s"],[42,45,"pl-corl"]],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[28,29,"pl-s"],[29,41,"pl-c1"],[41,42,"pl-s"],[129,130,"pl-ml"],[136,137,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,13,"pl-c1"],[13,14,"pl-s"],[16,17,"pl-s"],[17,28,"pl-c1"],[28,29,"pl-s"],[48,49,"pl-s"],[49,63,"pl-c1"],[63,64,"pl-s"],[130,131,"pl-s"],[131,142,"pl-c1"],[142,143,"pl-s"],[145,146,"pl-s"],[146,163,"pl-c1"],[163,164,"pl-s"],[305,306,"pl-ml"],[312,313,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[180,181,"pl-ml"],[187,188,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[17,18,"pl-s"],[18,33,"pl-c1"],[33,34,"pl-s"],[133,134,"pl-s"],[134,145,"pl-c1"],[145,146,"pl-s"],[267,268,"pl-ml"],[274,275,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,24,"pl-c1"],[24,25,"pl-s"],[33,34,"pl-s"],[34,44,"pl-c1"],[44,45,"pl-s"],[65,66,"pl-ml"],[172,173,"pl-ml"],[179,180,"pl-ml"]],[[0,1,"pl-ml"],[13,14,"pl-s"],[14,38,"pl-c1"],[38,39,"pl-s"],[131,135,"pl-s"],[132,135,"pl-corl"],[182,183,"pl-ml"],[189,190,"pl-ml"]],[[0,1,"pl-ml"],[271,272,"pl-ml"],[288,289,"pl-ml"]],[[0,1,"pl-ml"],[25,26,"pl-s"],[26,48,"pl-c1"],[48,49,"pl-s"],[208,209,"pl-ml"],[237,238,"pl-ml"]],[[0,1,"pl-ml"],[166,167,"pl-ml"],[173,174,"pl-ml"]],[],[[14,15,"pl-s"],[15,84,"pl-c1"],[84,85,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[36,37,"pl-s"],[37,83,"pl-c1"],[83,84,"pl-s"]],[[21,22,"pl-s"],[22,78,"pl-c1"],[78,79,"pl-s"]],[],[[35,39,"pl-s"],[36,39,"pl-corl"]],[[7,8,"pl-s"],[8,31,"pl-c1"],[31,32,"pl-s"],[37,38,"pl-s"],[38,42,"pl-c1"],[42,43,"pl-s"]],[],[[0,86,"pl-mh"],[3,86,"pl-en"],[70,74,"pl-s"],[71,74,"pl-corl"],[81,85,"pl-s"],[82,85,"pl-corl"]],[],[],[[7,11,"pl-s"],[8,11,"pl-corl"]],[],[],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[16,17,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[32,33,"pl-s"],[33,39,"pl-c1"],[39,40,"pl-s"],[88,89,"pl-s"],[89,95,"pl-c1"],[95,96,"pl-s"],[97,98,"pl-s"],[98,101,"pl-c1"],[101,102,"pl-s"],[103,104,"pl-s"],[104,109,"pl-c1"],[109,110,"pl-s"],[111,112,"pl-s"],[112,117,"pl-c1"],[117,118,"pl-s"],[121,122,"pl-s"],[122,139,"pl-c1"],[139,140,"pl-s"],[254,255,"pl-ml"],[261,262,"pl-ml"]],[[0,1,"pl-ml"],[14,15,"pl-s"],[15,37,"pl-c1"],[37,38,"pl-s"],[89,90,"pl-s"],[90,102,"pl-c1"],[102,103,"pl-s"],[136,137,"pl-s"],[137,138,"pl-s"],[154,155,"pl-s"],[155,156,"pl-s"],[168,169,"pl-s"],[169,178,"pl-c1"],[178,179,"pl-s"],[295,296,"pl-ml"],[302,303,"pl-ml"]],[[0,1,"pl-ml"],[6,7,"pl-s"],[7,26,"pl-c1"],[26,27,"pl-s"],[106,108,"pl-c1"],[162,163,"pl-s"],[163,182,"pl-c1"],[182,183,"pl-s"],[243,244,"pl-ml"],[250,251,"pl-ml"]],[[0,1,"pl-ml"],[241,242,"pl-ml"],[250,251,"pl-s"],[251,280,"pl-c1"],[280,281,"pl-s"],[290,291,"pl-ml"]],[[0,1,"pl-ml"],[250,251,"pl-ml"],[257,258,"pl-ml"]],[[0,1,"pl-ml"],[211,212,"pl-ml"],[218,219,"pl-ml"]],[],[[69,70,"pl-s"]],[[17,21,"pl-s"],[18,21,"pl-corl"]],[],[],[[14,15,"pl-s"],[15,27,"pl-c1"],[27,28,"pl-s"],[31,32,"pl-s"],[32,61,"pl-c1"],[61,62,"pl-s"]],[],[],[[37,38,"pl-s"],[38,63,"pl-c1"],[63,64,"pl-s"]],[[0,1,"pl-s"],[1,26,"pl-c1"],[26,27,"pl-s"],[39,40,"pl-s"],[40,61,"pl-c1"],[61,62,"pl-s"],[67,68,"pl-s"],[68,72,"pl-c1"],[72,73,"pl-s"]]],"colorizedLines":null}},"title":"2dph/PLAN.md at main · eSlider/2dph","appPayload":{},"meta":{"title":"2dph/PLAN.md at main · eSlider/2dph"}}</script>
  <div data-target="react-app.reactRoot"><meta name="github-code-view-meta-stats" id="github-code-view-meta-stats" data-hydrostats="publish"/> <!-- --> <a hidden="" id="code-view-repo-link" href="/eSlider/2dph" data-discover="true"></a> <div class="d-none"></div><div><div style="--spacing:var(--spacing-none)" class="prc-PageLayout-PageLayoutRoot--KH-d" data-component="SplitPageLayout" data-has-sidebar="true"><div class="prc-PageLayout-SidebarWrapper-kLG4B CopilotSidePanelSidebar-module__SidePanel__L3O0C CopilotSidePanelSidebar-module__HiddenSidePanel__TBRGn" style="--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="end" data-sticky="true" data-responsive-variant="fullscreen"><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-SidebarVerticalDivider-0Rl0V" data-component="PageLayout.VerticalDivider" data-variant="line" data-position="end" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="450" aria-valuemax="768" aria-valuenow="544" aria-valuetext="Pane width 544 pixels" tabindex="0"></div></div><div class="prc-PageLayout-Sidebar-iciWg" data-component="SplitPageLayout.Sidebar" data-resizable="true" style="--spacing:var(--spacing-normal);--pane-min-width:450px;--pane-max-width:768px;--pane-width-custom:544px;--pane-width-size:var(--pane-width-custom);--pane-width:544px"><div class="height-full" data-testid="copilot-code-view-side-panel"><div id="copilot-side-panel-content" class="height-full"></div></div></div></div><div class="prc-PageLayout-PageLayoutWrapper-2BhU2" data-width="full"><div class="prc-PageLayout-PageLayoutContent-BneH9"><div id="repos-file-tree-sidebar" class="CodeViewFileTreeLayout-module__sidebar__n_Aau" tabindex="0"><div class="prc-PageLayout-PaneWrapper-pHPop ReposFileTreePane-module__Pane__rBZpI ReposFileTreePane-module__HideTree__AYZnm ReposFileTreePane-module__HidePane__VHAVt" style="--offset-header:0px;--spacing-row:var(--spacing-none);--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="start" data-sticky="true"><div class="prc-PageLayout-HorizontalDivider-JLVqp prc-PageLayout-PaneHorizontalDivider-9tbnE" data-component="PageLayout.HorizontalDivider" data-variant-regular="none" data-variant-narrow="none" data-position="start" style="--spacing-divider:var(--spacing-none);--spacing:var(--spacing-none)"></div><div class="prc-PageLayout-Pane-AyzHK" data-component="SplitPageLayout.Pane" data-resizable="true" style="--spacing:var(--spacing-none);--pane-min-width:256px;--pane-max-width:calc(100vw - var(--pane-max-width-diff));--pane-width-size:var(--pane-width-large);--pane-width:320px"></div><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-PaneVerticalDivider-le57g" data-component="PageLayout.VerticalDivider" data-variant-narrow="none" data-variant-regular="line" data-variant-wide="line" data-position="start" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="256" aria-valuemax="600" aria-valuenow="320" aria-valuetext="Pane width 320 pixels" tabindex="0"></div></div></div></div><div data-component="SplitPageLayout.Content" class="prc-PageLayout-ContentWrapper-gR9eG"><div class="prc-PageLayout-Content-xWL-A" data-width="full" style="--spacing:var(--spacing-none)"><div class="SharedPageLayout-module__content__IwGAp" data-selector="repos-split-pane-content" id="repos-split-pane-content" tabindex="0"> <!-- --> <div class="container CodeViewHeader-module__Box__JkPOb"><div class="CodeViewHeader-module__StickyHeader__Qn7UN" id="StickyHeader"><div class="CodeViewHeader-module__Box_1__SbNDV"><div class="CodeViewHeader-module__Box_2__TB46f"><div class="react-code-view-header-wrap--narrow CodeViewHeader-module__Box_3__q1zUL"><div class="CodeViewHeader-module__treeToggleWrapper__RQ__9"><h2 class="use-tree-pane-module__Heading__s4QbZ prc-Heading-Heading-MtWFE" data-component="Heading"><button data-component="Button" type="button" aria-label="Expand file tree" data-testid="expand-file-tree-button-mobile" class="prc-Button-ButtonBase-9n-Xk ExpandFileTreeButton-module__Button_1__Svs95" data-loading="false" data-size="medium" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-left" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.78 12.53a.75.75 0 0 1-1.06 0L2.47 8.28a.75.75 0 0 1 0-1.06l4.25-4.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L4.81 7h7.44a.75.75 0 0 1 0 1.5H4.81l2.97 2.97a.75.75 0 0 1 0 1.06Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Files</span></span></button><button data-component="IconButton" type="button" data-testid="expand-file-tree-button" aria-controls="repos-file-tree" class="prc-Button-ButtonBase-9n-Xk position-relative ExpandFileTreeButton-module__expandButton__hDOcv ExpandFileTreeButton-module__filesButtonBreakpoint__zEvz3 fgColor-muted prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="invisible" aria-labelledby="_R_4lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sidebar-collapse" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"></path><path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="se" data-component="Tooltip" aria-hidden="true" id="_R_4lla9lik5_">Expand file tree</span><div class="d-none"></div></h2></div><div class="react-code-view-header-mb--narrow mr-2"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector-wide"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="react-code-view-header-mb--narrow CodeViewHeader-module__Box_5__MQ0hL"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__lg__Rjz0A"><nav data-testid="breadcrumbs" aria-labelledby="repos-header-breadcrumb-heading" id="repos-header-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="repos-header-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/eSlider/2dph/tree/main" data-discover="true">2dph</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__lg__Rjz0A" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__lg__Rjz0A prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="file-name-id">PLAN.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lla9lik5_">Copy path</span></div></div></div><div class="react-code-view-header-element--wide"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><div><div class="CodeViewHeader-module__FileResultsList__JDzUy"><span class="d-flex FileResultsList-module__FilesSearchBox__ivVkc TextInput-wrapper prc-components-TextInputWrapper-Hpdqi prc-components-TextInputBaseWrapper-wY-n0" data-no-trailing-action="true" data-component="TextInput" data-leading-visual="true" data-trailing-visual="true" aria-busy="false"><span class="TextInput-icon" id="_R_1cpla9lik5_" aria-hidden="true" data-component="TextInput.LeadingVisual"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg></span><input type="text" aria-label="Go to file" role="combobox" aria-controls="file-results-list" aria-expanded="false" aria-haspopup="dialog" autoCorrect="off" spellCheck="false" placeholder="Go to file" aria-describedby="_R_1cpla9lik5_ _R_1cpla9lik5H1_" data-component="input" class="prc-components-Input-IwWrt" value=""/><span class="TextInput-icon" id="_R_1cpla9lik5H1_" aria-hidden="true" data-component="TextInput.TrailingVisual"></span></span></div><div class="d-none"></div></div><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-wide" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7p9la9lik5_" id="_R_99la9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7p9la9lik5_">More file actions</span></div></div></div><div class="react-code-view-header-element--narrow"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-narrow" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7pdla9lik5_" id="_R_9dla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7pdla9lik5_">More file actions</span></div></div></div></div></div></div></div><div class="CodeView-module__contentWrapper__cG2JH"><div class="react-code-view-bottom-padding"><div class="BlobTopBanners-module__Box__v_nvx"></div></div> <div class="d-none"></div><div class="d-flex flex-column border rounded-2 tmp-mb-3 pl-1"><div class="LatestCommit-module__Box__B25ZT"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">Latest commit</h2><div style="width:120px" class="Skeleton Skeleton--text" data-testid="loading"> </div><div class="d-flex flex-shrink-0 gap-2"><div data-testid="latest-commit-details" class="d-none d-sm-flex flex-items-center"></div><div class="d-flex gap-2"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">History</h2><a data-component="LinkButton" href="/eSlider/2dph/commits/main/PLAN.md" class="prc-Button-ButtonBase-9n-Xk d-none d-lg-flex LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><span class="fgColor-default">History</span></span></span></a><div class="d-sm-none"></div><div class="d-flex d-lg-none"><a data-component="LinkButton" aria-label="View commit history for this file." href="/eSlider/2dph/commits/main/PLAN.md" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible" aria-describedby="_R_4mlala9lik5_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span></span></a><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" role="tooltip" aria-hidden="true" id="_R_4mlala9lik5_">History</span></div></div></div></div></div><div class="d-flex flex-row"><div class="container BlobViewContent-module__blobContainer__DtH2d"><div class="react-code-size-details-banner BlobViewContent-module__codeSizeDetails__e5sUw"><div class="react-code-size-details-banner CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="95.3 KB" style="--truncate-max-width:100%"><span>976 lines (806 loc) · 95.3 KB</span></div></div></div></div><div class="react-blob-view-header-sticky BlobViewContent-module__stickyHeader__VwxB5" id="repos-sticky-header"><div class="BlobViewHeader-module__Box__yhm9u"><div class="react-blob-sticky-header"><div class="FileNameStickyHeader-module__outerWrapper__ZL4Xc FileNameStickyHeader-module__outerWrapperHidden__Zpynk"><div class="FileNameStickyHeader-module__Box_1__Hazu5"><div class="FileNameStickyHeader-module__Box_2__hoolP"><div class="FileNameStickyHeader-module__Box_3__MVKsk"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="FileNameStickyHeader-module__Box_4__FLhtt"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__md__Wb1Gs"><nav data-testid="breadcrumbs" aria-labelledby="sticky-breadcrumb-heading" id="sticky-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="sticky-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/eSlider/2dph/tree/main" data-discover="true">2dph</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__md__Wb1Gs" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__md__Wb1Gs prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="sticky-file-name-id">PLAN.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lcpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lcpala9lik5_">Copy path</span></div></div></div><button data-component="Button" type="button" class="prc-Button-ButtonBase-9n-Xk FileNameStickyHeader-module__Button__LSEU_ FileNameStickyHeader-module__GoToTopButton__nxAFn" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-up" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.47 7.78a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018L9 4.81v7.44a.75.75 0 0 1-1.5 0V4.81L4.53 7.78a.75.75 0 0 1-1.06 0Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Top</span></span></button></div></div></div><div class="BlobViewHeader-module__Box_1__VEmuQ"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">File metadata and controls</h2><div class="BlobViewHeader-module__Box_2__icUs2"><ul aria-label="File view" class="prc-SegmentedControl-SegmentedControl-lqIXp BlobTabButtons-module__SegmentedControl__jen2u" data-variant="default" data-size="small" data-component="SegmentedControl"><li class="prc-SegmentedControl-Item-tSCQh" data-selected="" data-component="SegmentedControl.Button"><button aria-pressed="true" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:transparent"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Preview">Preview</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Code">Code</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Blame">Blame</div></span></button></li></ul><div class="d-none"></div><div class="react-code-size-details-in-header CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="95.3 KB" style="--truncate-max-width:100%"><span>976 lines (806 loc) · 95.3 KB</span></div></div></div></div><div class="BlobViewHeader-module__Box_3__ng6v2"><div class="d-none"></div><div class="react-blob-header-edit-and-raw-actions BlobViewHeader-module__Box_4__J4Y4W"><div class="d-none"></div><div class="prc-ButtonGroup-ButtonGroup-vFUrY" data-component="ButtonGroup"><div class="prc-ButtonGroup-Item-PqvDl"><a data-component="LinkButton" href="https://github.com/eSlider/2dph/raw/refs/heads/main/PLAN.md" data-testid="raw-button" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov BlobViewHeader-module__LinkButton__X9kx2" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Raw</span></span></a></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="copy-raw-button" class="prc-Button-ButtonBase-9n-Xk prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_qaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_qaucpala9lik5_">Copy raw file</span></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="download-raw-button" class="prc-Button-ButtonBase-9n-Xk BlobViewHeader-module__downloadButton__ef459 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_eaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-download" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_eaucpala9lik5_">Download raw file</span></div></div></div><button data-component="IconButton" type="button" aria-pressed="false" class="prc-Button-ButtonBase-9n-Xk tmp-mr-2 TableOfContents-module__IconButton__jrlNM prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_3ucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-list-unordered" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.75 2.5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5ZM2 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-6a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_3ucpala9lik5_">Outline</span><div class="react-blob-header-edit-and-raw-actions-combined"><button data-component="IconButton" type="button" title="More file actions" data-testid="more-file-actions-button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click BlobViewHeader-module__IconButton__XrMQY prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_fkecpala9lik5_" id="_R_kecpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_fkecpala9lik5_">Edit and raw actions</span></div></div></div></div><div></div></div><div class="BlobViewContent-module__blobContentWrapper__JS0W6"><section aria-labelledby="file-name-id-wide file-name-id-mobile" class="BlobContent-module__blobContentSection__VOgZq BlobContent-module__blobContentSectionMarkdown__mPLOK" style="margin-top:46px"><div class="js-snippet-clipboard-copy-unpositioned BlobContent-module__markdownBlob__T8jpG" data-hpc="true" containertiming="hpc"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 tabindex="-1" class="heading-element" dir="auto">PLAN — 2dph (deductionphile)</h1><a id="user-content-plan--2dph-deductionphile" class="anchor" aria-label="Permalink: PLAN — 2dph (deductionphile)" href="#plan--2dph-deductionphile"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">A brain that loves facts and deduction. Evidence-first knowledge graph + hybrid
RAG over the operational Brain/ops/eSlider stack. Built like Sherlock
Holmes: nothing is asserted unless it has proof.</p>
<p dir="auto">Status: <strong>v1 in</strong> (epic <a href="https://git.produktor.io/eSlider/2dph/issues/16" rel="nofollow">#16</a> closed).
v2 board: milestone <a href="https://git.produktor.io/eSlider/2dph/milestone/13" rel="nofollow">v2</a> —
OCR <a href="https://git.produktor.io/eSlider/2dph/issues/6" rel="nofollow">#6</a> in,
<a href="https://git.produktor.io/eSlider/2dph/issues/29" rel="nofollow">#29</a> OQ1 in,
<a href="https://git.produktor.io/eSlider/2dph/issues/30" rel="nofollow">#30</a> OQ3 in,
<a href="https://git.produktor.io/eSlider/2dph/issues/34" rel="nofollow">#34</a> D23 in,
<a href="https://git.produktor.io/eSlider/2dph/issues/36" rel="nofollow">#36</a> OQ5/D24 in,
<a href="https://git.produktor.io/eSlider/2dph/issues/102" rel="nofollow">#102</a> gs PDF normalize in,
<a href="https://git.produktor.io/eSlider/2dph/issues/163" rel="nofollow">#163</a> browser-sync in
(<code>bin/cron/browser-sync.go</code> + 6-hourly systemd timer, reads
<code>var/corpus/{gmail,linkedin,djinni}</code> → POST /ingest; Thorium down tolerated),
<a href="https://git.produktor.io/eSlider/2dph/issues/82" rel="nofollow">#82</a> Synapse Matrix (leafs+edges
as a service for pc-agent) in — <code>bin/brain/synapse-matrix.go</code>, docs
<a href="/eSlider/2dph/blob/main/docs/brain-synapse.md">docs/brain-synapse.md</a>.
Gap: <a href="/eSlider/2dph/blob/main/docs/roadmap.md">docs/roadmap.md</a>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">What</h2><a id="user-content-what" class="anchor" aria-label="Permalink: What" href="#what"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">A single embedded store (LadybugDB, one <code>.lbug</code> file) that holds:</p>
<ul dir="auto">
<li>a <strong>property graph</strong> (Cypher) over the ops corpus and portfolio,</li>
<li><strong>FTS5</strong> (BM25) and <strong>HNSW vector</strong> indexes (model2vec embeddings),</li>
<li>every node/edge annotated with <code>root</code> (<code>facts</code> | <code>info</code>), <code>confidence</code>
(<code>confirmed</code> | <code>partial</code> | <code>hypothesis</code>), <code>evidence[]</code>, <code>how</code>, <code>where</code>, <code>when</code>.</li>
</ul>
<p dir="auto">Search = <strong>deduction</strong>: facts root first, info root second, <code>web-search</code> as the
second independent source when local roots cannot confirm. Following the
detective method: <strong>a fact needs ≥2 independent sources or it is
<code>(not confirmed)</code>.</strong></p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Decisions (recorded)</h2><a id="user-content-decisions-recorded" class="anchor" aria-label="Permalink: Decisions (recorded)" href="#decisions-recorded"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>#</th>
<th>Question</th>
<th>Answer</th>
</tr>
</thead>
<tbody>
<tr>
<td>D1</td>
<td>RAG corpus</td>
<td>ops stack (chat, onlyoffice, gitea/NPM, searchxng, observability, ai-bot, mcp-servers, <code>~/.ssh/config</code>) + portfolio. Exclude <code>office.dev</code> + jobs/applications.</td>
</tr>
<tr>
<td>D2</td>
<td>skill merging</td>
<td>integrate skills <strong>in this project</strong> <code>skills/</code>; skip gitea / brain-dependent skills.</td>
</tr>
<tr>
<td>D3</td>
<td>web search</td>
<td>Go client <code>bin/web/search.go</code> (<code>internal/websearch</code>). SearXNG URL is config (<code>BRAIN_SEARCH_URL</code>). Optional Compose profile <code>searxng</code> (sanitized settings). Do not run a second copy on a host that already has one. Empty/<code>throttled</code> ≠ “nothing exists”.</td>
</tr>
<tr>
<td>D4</td>
<td>embeddings</td>
<td><strong>model2vec</strong> <code>minishlab/potion-multilingual-128M</code> instead of embeddinggemma.</td>
</tr>
<tr>
<td>D5</td>
<td>parser</td>
<td><strong>mistune</strong> for MD → leaf extraction (duckdb-md documented as future optional SQL/export layer, not v1).</td>
</tr>
<tr>
<td>D6</td>
<td>graph engine</td>
<td><strong>LadybugDB</strong>. Go is the service (<code>bin/brain/search.go</code>, <code>bin/brain/serve.go</code> in-process, <code>internal/brain</code>). Read path is Go + Zig CGO (D21). Python <code>bin/kb/{get,stats,eval}</code> is the CI fallback when Zig/libs are not fetched. Incremental write is Python <code>bin/kb/add</code> (<code>bin/brain/add.go</code>). Bulk rebuild stays <code>compose --profile index</code> until the Go write path is safe.</td>
</tr>
<tr>
<td>D7</td>
<td>db access</td>
<td><code>db-yaml</code>/<code>psql-yq</code>-style, read-only, YAML out. OnlyOffice Postgres via SSH tunnel (<code>127.0.0.1:5433</code>).</td>
</tr>
<tr>
<td>D8</td>
<td>evidence</td>
<td>detective method: ≥2 independent sources or <code>(not confirmed)</code>. 2-source auto-pair docker ps × compose × ssh-config × docs.</td>
</tr>
<tr>
<td>D9</td>
<td>facts/goal model</td>
<td>Who / What / How / Where / When + evidence + confidence on every edge.</td>
</tr>
<tr>
<td>D10</td>
<td>versioning</td>
<td>everything is a leaf with <code>sha256 + observed_at + source_rev</code>; <code>File-[:HAS_VERSION]-&gt;Commit-[:AUTHORED]-&gt;Person</code>. Stale = <code>source_rev</code> &lt; git HEAD.</td>
</tr>
<tr>
<td>D11</td>
<td>strong/weak</td>
<td><code>root</code> column: <code>facts</code> (strong) vs <code>info</code> (weak). Answer is <code>confirmed</code> only from facts root.</td>
</tr>
<tr>
<td>D12</td>
<td>transactional</td>
<td>facts and info split by root but <strong>written in the same Ladybug transaction (ACID)</strong> on every write.</td>
</tr>
<tr>
<td>D13</td>
<td>portfolio</td>
<td>start graph <code>(Person:eslider)-[:HAS]-&gt;(Portfolio)</code>, associate other natural/juristic persons later.</td>
</tr>
<tr>
<td>D14</td>
<td>tooling style</td>
<td><code>bin/{subject}/{method}.go</code> shebang (e.g. <code>bin/brain/search.go</code>). Shared code in <code>internal/</code>. One root <code>go.mod</code> + <code>go.work</code>. No <code>bin/*/main.go</code>, no nested modules.</td>
</tr>
<tr>
<td>D15</td>
<td>repo</td>
<td>Gitea <a href="https://git.produktor.io/eSlider/2dph" rel="nofollow"><code>eSlider/2dph</code></a> is origin + <a href="https://git.produktor.io/eSlider/2dph/issues" rel="nofollow">issues</a>. GitHub <code>eSlider/2dph</code> is the public clone (PRs + Actions CI). No direct <code>main</code> pushes. TDD → PR → CI green → merge.</td>
</tr>
<tr>
<td>D16</td>
<td>contradictions</td>
<td>≥2 yes vs ≥2 no → hypothesis → <code>(not confirmed)</code> until a rule fires. Order: <strong>temporal_freshness</strong> (fresh ≥2 vs stale minority), then <strong>authority_pairing</strong> (runtime/config A×B beats narrative C). Store as <code>a x b vs c x d</code> on hypothesis leafs. <code>bin/facts/audit contradict</code>. <a href="https://git.produktor.io/eSlider/2dph/issues/29" rel="nofollow">#29</a>.</td>
</tr>
<tr>
<td>D17</td>
<td>assertion gate</td>
<td>Fact-check every <em>claim</em> (facts → info → live → web), not every edit. <code>bin/brain/search.go</code> adds a <code>web</code> block when there is no facts hit (<code>throttled</code>/<code>skipped</code>/<code>refused</code> ≠ absence). <code>--root</code> and <code>--no-web</code> stay local. Missing graph ≠ “does not exist”.</td>
</tr>
<tr>
<td>D18</td>
<td>reasoner</td>
<td>Pluggable OpenAI-compatible URL (<code>REASONER_BASE_URL</code>). RAM: <code>Qwen/Qwen3.5-9B</code>. Quality: <code>prism-ml/Bonsai-27B-gguf</code> or <code>Qwen/Qwen3.6-27B</code>. No official Qwen3.6-9B. CPU bake-off: <code>bin/reasoner/bench.go</code> + compose profile <code>reasoner</code> (<code>OLLAMA_NUM_GPU=0</code>, <code>:11435</code>). PicoClaw is compose profile <code>picoclaw</code>; tools are <code>search</code>/<code>get</code>/<code>audit</code>. Weights are not copied into the 2dph image. Agent lever/loop: <a href="https://git.produktor.io/eSlider/2dph/issues/15" rel="nofollow">#15</a>.</td>
</tr>
<tr>
<td>D19</td>
<td>git history</td>
<td><a href="https://github.com/go-git/go-git">go-git</a> via <code>bin/brain/import-git.go</code> (lib <code>internal/gitlog</code>). No subprocess of the git binary. Reads history and upserts commit leafs into the brain directly (<code>--dry-run</code> to preview).</td>
</tr>
<tr>
<td>D20</td>
<td>agent API</td>
<td>OpenAPI + MCP are generated from the same <code>pkg/httpapi.Ops</code> table as <code>bin/brain/serve.go</code> handlers. <code>GET /openapi.json</code>, <code>POST /mcp</code> (JSON-RPC tools/list + tools/call). Tool names match OpenAPI paths (<code>search</code>/<code>get</code>/<code>stats</code>/<code>audit</code>/<code>ingest</code>).</td>
</tr>
<tr>
<td>D21</td>
<td>CGO</td>
<td>Ladybug/tokenizers CGO is compiled with <strong>Zig</strong> (<code>bin/cgo/zcc</code> → <code>zig cc -target …-linux-gnu</code>), not gcc. <code>bin/cgo/zig</code> pins Zig 0.14.1 + liblbug 0.19.1 + libtokenizers 1.27.0. Compose <code>target: api</code> has no CPython; write/rebuild is profile <code>index</code>.</td>
</tr>
<tr>
<td>D22</td>
<td>analytics</td>
<td><strong>duckdb-go</strong> in-process (<code>pkg/duckdb</code>, <code>bin/jsonl/stats.go</code>) for quantiles/JSONL. Links with <strong>gcc/g++</strong>, not Zig. Ladybug stays the graph; web-search cache stays modernc sqlite. Slice small structured docs with <strong>mikefarah/yq</strong>, not kislyuk/jq. <a href="https://git.produktor.io/eSlider/2dph/issues/30" rel="nofollow">#30</a>.</td>
</tr>
<tr>
<td>D23</td>
<td>CLI</td>
<td><strong>flaggy</strong> (<code>github.com/integrii/flaggy</code>, 0 deps). Flags at any position. Wrapper <code>pkg/cli</code>. Bash complete: <code>source &lt;(./bin/shell/complete.go bash)</code>. No cobra, no stdlib <code>flag</code> in Go tools. Search does not intercept the word <code>completion</code>. <a href="https://git.produktor.io/eSlider/2dph/issues/34" rel="nofollow">#34</a>.</td>
</tr>
<tr>
<td>D24</td>
<td>fact intervals</td>
<td>Leaf <code>valid_from</code> / <code>valid_to</code> (YYYY-MM-DD, inclusive; empty = open/legacy). Search <code>--as-of</code> / MCP <code>as_of</code> keeps facts active that day. Not D16 <code>temporal_freshness</code> (source stale vs HEAD). Empty interval = always visible. <a href="https://git.produktor.io/eSlider/2dph/issues/36" rel="nofollow">#36</a>.</td>
</tr>
<tr>
<td>D25</td>
<td>deploy data path</td>
<td>Brain serves from host <code>var/</code>, not named volumes. Compose binds <code>./var:/data/var</code> (kb.lbug at <code>/data/var/kb.lbug</code>), HF model from <code>var/hf</code>, Ladybug FTS/VECTOR extensions mounted read-only into <code>$HOME/.lbdb/extension</code>. <code>brain</code> uses <code>network_mode: host</code> so <code>127.0.0.1:8630</code> works with any image (KB_HOST-independent). Named volumes <code>kb-model</code>/<code>kb-var</code> dropped — live data is host <code>var/</code> (gitignored).</td>
</tr>
<tr>
<td>D26</td>
<td>search A/B (#201/#202)</td>
<td>Ничего не «лучше» без A/B. <code>bin/brain/bench.go</code> + golden-set (<code>internal/brain/testdata/golden-set.json</code>, ~50 запросов, рус+англ, факты/mail/docs/git/ssh). Метрики: latency p50/p95/mean, recall@5/@10 (fragment-гейт как eval.go + A/B vs baseline IDs), CPU/RSS из /proc. Гейты: recall@5 ≥ 0.95, кандидат p50 ≤ baseline × 1.5. Baseline: линейный скан 313k = p50 ~31s. Кандидаты (ANN/SIMD/FTS-first) — child issues эпика #201.</td>
</tr>
<tr>
<td>D32</td>
<td>ANN вне liblbug (#201/#204)</td>
<td>Инкрементальный векторный индекс в <code>internal/brain/ann</code>: <strong>IVF</strong> (k-means + точный косинус по probed-ячейкам, чистый Go; <code>coder/hnsw</code>/HNSW отброшен — на 313k реальных достижимы 34% узлов, recall@5 0.16). Индекс <code>var/state/vector.ann</code> + append-only WAL (<code>upsert</code> = только новые leafs, без rebuild). <code>bin/brain/ann.go</code> (build/upsert/ensure/stats/api/search), wave-шаг <code>ann-build</code> (#206). <code>queryVector</code>: индекс top-k → point-lookup метаданных → слияние с FTS (rank не тронут); fallback на скан при отсутствии/повреждении. Конфиг <code>vector.ann</code> (NList=2000, NProbe=128 — дефолты пакета; прод-конфиг nprobe=2000, решение #206). A/B: baseline p50 ~31s → кандидат p50 ~0.4s, recall@5 vs baseline 1.0 (цифры в #204/#206).</td>
</tr>
<tr>
<td>D26</td>
<td>repo layout</td>
<td><code>bin/{subject}</code> = tools only (<code>{verb}-{object}.go</code>); executables/orchestration → <code>scripts/</code>; data tree <code>var/{tmp,dist,log,state,cache,hf,kb.lbug,corpus/{git,mail,chats,docs}}</code>; configs <code>etc/{subject}</code> + central <code>etc/brain/config.yml</code>. CI guard fails on absolute paths and Python remnants. <a href="https://git.produktor.io/eSlider/2dph/issues/89" rel="nofollow">#89</a></td>
</tr>
<tr>
<td>D27</td>
<td>config system</td>
<td>All external sources (paths, URLs, endpoints) via <code>github.com/eslider/go-config</code> (<code>internal/config</code>): stack <code>etc/brain/config.yml</code> → <code>config.local.yml</code> → <code>.env</code> → process env; one load per process into a strict typed struct; deep merge (maps recurse, scalars last-write-wins, keys lower+alnum); no ad-hoc <code>os.Getenv</code>; legacy <code>KB_*</code> / <code>BRAIN_SEARCH_*</code> env names honored transitionally. <a href="https://git.produktor.io/eSlider/2dph/issues/90" rel="nofollow">#90</a>, <a href="https://git.produktor.io/eSlider/2dph/issues/91" rel="nofollow">#91</a></td>
</tr>
<tr>
<td>D28</td>
<td>no hardcoded paths/URLs</td>
<td>Nothing like <code>/mnt/…</code>, <code>/home/&lt;user&gt;/…</code>, or pinned host URLs in code/comments/tests; missing config key = explicit error pointing at <code>etc/brain/config.yml</code>. CI-enforced. <a href="https://git.produktor.io/eSlider/2dph/issues/89" rel="nofollow">#89</a></td>
</tr>
<tr>
<td>D29</td>
<td>typed code</td>
<td>Static typing first: no <code>map[string]any</code> at boundaries; loose→strict via go-viper/mapstructure/v2; primitives only in <code>pkg/utils/*.go</code>. Hotspots listed in <a href="https://git.produktor.io/eSlider/2dph/issues/92" rel="nofollow">#92</a>.</td>
</tr>
<tr>
<td>D30</td>
<td>owned repos</td>
<td>All <code>github.com/eSlider/go-*</code> are owned: Gitea <code>git.produktor.io/eSlider/*</code> = working home (issues/releases only there), GitHub = push-mirror; module paths stay <code>github.com/eSlider/go-*</code>. Shared template: subpackage-per-domain. Gitea fetch via <code>replace</code> in <code>go.mod</code>: <code>go-config@v0.4.0</code>, <code>go-onlyoffice@v0.14.0</code> → <code>git.produktor.io/eSlider/*</code> (go.sum carries Gitea hashes only; zero <code>github.com/eSlider/go-*</code> downloads). <a href="https://git.produktor.io/eSlider/2dph/issues/101" rel="nofollow">#101</a></td>
</tr>
<tr>
<td>D31</td>
<td>concurrency</td>
<td>Public APIs synchronous; ctx-first IO; bounded pools; wg-accounted goroutines; sender closes channels; errgroup for fan-out/fan-in; backpressure over unbounded queues; graceful shutdown; <code>go test -race</code> in CI. <a href="https://git.produktor.io/eSlider/2dph/issues/94" rel="nofollow">#94</a></td>
</tr>
<tr>
<td>D32</td>
<td>zero-alloc hot paths</td>
<td>Pre-allocation, buffer reuse (<code>buf[:0]</code>), <code>sync.Pool</code> with reset, <code>strings.Builder</code> over <code>+</code>, sets as <code>map[K]struct{}</code>, structs over maps; proven by <code>-benchmem</code>. Applies to hot paths only — no premature optimization.</td>
</tr>
<tr>
<td>D33</td>
<td>sync-ETL reimplementation</td>
<td>Pipeline <code>Source.Fetch(ctx,cursor)→[]Blob→Registry.Decode→Transform→Load</code>; one Handler per format (eml=emersion/go-message <code>Walk()</code>, zip=<code>archive/zip</code>, html=<code>x/net/html</code> optional, pdf=Ghostscript normalize→pdftotext/tesseract); lazy children, walker safety limits; single implementation per transformer; canonical <code>Conversation</code> model on disk (<code>var/corpus</code>) → brain graph (<code>SENT/TO/CC/BCC/REPLY_TO/PART_OF</code>); URL addressing <code>scheme://platform/path[#anchor]</code> with <code>sha256(URL)[:16]</code> node IDs. Epic <a href="https://git.produktor.io/eSlider/2dph/issues/88" rel="nofollow">#88</a>: <a href="https://git.produktor.io/eSlider/2dph/issues/95" rel="nofollow">#95</a>–<a href="https://git.produktor.io/eSlider/2dph/issues/102" rel="nofollow">#102</a>.</td>
</tr>
<tr>
<td>D34</td>
<td>synapse edges</td>
<td>User-defined edges between neurons modeled as a <code>SYNAPTIC (FROM Leaf TO Leaf, type STRING)</code> rel table (leafs = neurons, edges = synapses); adjacency = both directions; path = <code>[:SYNAPTIC* ALL SHORTEST 1..N]</code> (Ladybug exposes only intermediate nodes → rebuild <code>[from, mid…, to]</code>). Service <code>bin/brain/synapse-matrix.go</code>, auth = token OR loopback bind, config section <code>synapse</code>. <a href="https://git.produktor.io/eSlider/2dph/issues/82" rel="nofollow">#82</a></td>
</tr>
<tr>
<td>D35</td>
<td>ANN в проде (#206)</td>
<td>Победитель A/B (#204) включён по умолчанию: <code>vector.ann.enabled: true</code> (serve + CLI), <strong>nprobe=2000</strong> — замерено на живом корпусе 313689 векторов (11 запросов golden-set): p50 471ms recall@5=1.0 vs 256 (p50 262ms recall@5 0.818) — корректность сначала, тюнинг позже. Волна: шаг <code>ann-build</code> = <code>ann.go ensure</code> — полный build только когда индекса нет/устарел (&gt;10% отставания по счётчику leafs), иначе инкрементальный WAL-upsert (+100 &lt;1s, никогда rebuild на волну). <code>serve</code> — тёплый старт: индекс грузится при старте (WarmANN), при пустом/битом — fallback на скан, build делает волна (single-writer, #204). A/B прод: p50 471ms vs baseline 33.6s (0.014x), recall@5 vs baseline 1.0.</td>
</tr>
<tr>
<td>D36</td>
<td>FTS-фаза rebuild (#244)</td>
<td>После write-фазы <code>brain-index</code> закрывает хэндл и переоткрывает БД с автоподобранным пулом <code>max(1GB, chars×32)</code> для <code>CREATE_FTS_INDEX</code>: на полном корпусе (280MB текста) 1GB-пул падает и оставляет орфан <code>0_id_appears_info</code>, недостижимый через SQL (внутренний каталог). Замеры порога: 10.3MB→64/128MB, 10.8MB→192/256MB, прод 280MB→1GB/10GB. Двухфазный workaround из rebuild.md автоматизирован (отменён как ручной). C-потолок (замер #244): write-фаза ~140KB/leaf линейно, пул 1GB не ограничивает, CHECKPOINT/embedding не влияют, close не освобождает в том же процессе; FTS — пул ~9GB на полном корпусе. Критерий #237 (RSS ≤2-4GB) переформулирован: Go-часть — да; C-сторона — задокументированный потолок (~14-15GB write + ~9GB FTS на 105k).</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Architecture</h2><a id="user-content-architecture" class="anchor" aria-label="Permalink: Architecture" href="#architecture"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="2dph/
  PLAN.md / AGENTS.md
  docs/                     published docs (this conversation → docs/ as md)
  skills/                   in-project skills (web-search, postgres, brain, picoclaw, diataxis-docs)
  bin/
    brain/search.go         deduction: facts → info → web
    brain/index.go          rebuild FTS + HNSW (incl. --with-mail)
    brain/add.go            incremental leaf write (no rebuild)
    brain/serve.go          HTTP API in-process + OpenAPI/MCP (D20); Zig CGO (D21)
    brain/get.go stats.go eval.go watch.go model.go   # Go read/ops (cgo)
    facts/extract.go audit.go crm.go  # 2-source pairing, confidence, CRM proof
    mail/sync.go import.go convert-mbox.go ocr.go    # mail ETL (Gmail/OO/M365), PDF OCR
    mail/incubator.go       # legacy .eml → doveadm save в mail-server-инкубатор (#252/эпик #250)
    chats/sync.go import.go facts.go apply.go  # conversations (no chats index)
    contacts/*.go           CRM contacts importer
    web/search.go           SearXNG client (throttled ≠ absence)
    git/import.go           go-git history (no git binary; conversion only)
    markdown/import.go      H2 leaf split (Go)
    postgres/query.go       read-only YAML (wraps scripts/db/psql-yq)
    reasoner/bench.go        CPU tool-call bench (D18; OpenAI tools)
    qa/stats.go             DuckDB quantiles / JSONL (D22; not the test taxonomy)
    semver/next.go          next semver from conventional commits (Release job)
    cli/complete.go         flaggy bash/zsh/fish complete (D23)
    cgo/zig zcc zc++        CGO toolchain (zig cc, not gcc)
    stack/start start-assistant start-mail-sync stop status
    db/psql-yq              (vendored)
    ssh-tunnel              onlyoffice pg tunnel 5433
  test/
    system/                 offline-gated system tests (CI default)
    stress/                 live-brain load generator
    integration/            opt-in live-dependency tests
    README.md               how to run each tier
  etc/
    searxng/ picoclaw/      operator config (FHS; moved from deploy/)
  var/kb.lbug               single embedded store (gitignored)"><pre class="notranslate"><code>2dph/
  PLAN.md / AGENTS.md
  docs/                     published docs (this conversation → docs/ as md)
  skills/                   in-project skills (web-search, postgres, brain, picoclaw, diataxis-docs)
  bin/
    brain/search.go         deduction: facts → info → web
    brain/index.go          rebuild FTS + HNSW (incl. --with-mail)
    brain/add.go            incremental leaf write (no rebuild)
    brain/serve.go          HTTP API in-process + OpenAPI/MCP (D20); Zig CGO (D21)
    brain/get.go stats.go eval.go watch.go model.go   # Go read/ops (cgo)
    facts/extract.go audit.go crm.go  # 2-source pairing, confidence, CRM proof
    mail/sync.go import.go convert-mbox.go ocr.go    # mail ETL (Gmail/OO/M365), PDF OCR
    mail/incubator.go       # legacy .eml → doveadm save в mail-server-инкубатор (#252/эпик #250)
    chats/sync.go import.go facts.go apply.go  # conversations (no chats index)
    contacts/*.go           CRM contacts importer
    web/search.go           SearXNG client (throttled ≠ absence)
    git/import.go           go-git history (no git binary; conversion only)
    markdown/import.go      H2 leaf split (Go)
    postgres/query.go       read-only YAML (wraps scripts/db/psql-yq)
    reasoner/bench.go        CPU tool-call bench (D18; OpenAI tools)
    qa/stats.go             DuckDB quantiles / JSONL (D22; not the test taxonomy)
    semver/next.go          next semver from conventional commits (Release job)
    cli/complete.go         flaggy bash/zsh/fish complete (D23)
    cgo/zig zcc zc++        CGO toolchain (zig cc, not gcc)
    stack/start start-assistant start-mail-sync stop status
    db/psql-yq              (vendored)
    ssh-tunnel              onlyoffice pg tunnel 5433
  test/
    system/                 offline-gated system tests (CI default)
    stress/                 live-brain load generator
    integration/            opt-in live-dependency tests
    README.md               how to run each tier
  etc/
    searxng/ picoclaw/      operator config (FHS; moved from deploy/)
  var/kb.lbug               single embedded store (gitignored)
</code></pre></div>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Schema (first pass)</h2><a id="user-content-schema-first-pass" class="anchor" aria-label="Permalink: Schema (first pass)" href="#schema-first-pass"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Node tables: <code>Person, Service, Host, Container, Repo, File, Commit, Leaf</code>.
<code>Leaf(embedding FLOAT[N])</code> — FTS on <code>text</code>. Векторный поиск: ANN IVF вне
liblbug (<code>internal/brain/ann</code>, #204; штатный liblbug HNSW Leaf_vec крашится
на росте графа, #192 → встроенный скан как fallback).
Edges: <code>RUNS / USES / FROM_FILE / HAS_VERSION / AUTHORED / ABOUT / ASSOCIATED / SIMILAR_0.85</code>.
<code>FROM_FILE</code> / <code>HAS_VERSION</code> / <code>AUTHORED</code>: <code>bin/brain/search.go --hop N</code> walks
them from each hit (1=File, 2=Commit, 3=Person). Rebuild writes
<code>Leaf-[:FROM_FILE]-&gt;File</code>; git import writes the rest.</p>
<p dir="auto">Common props on every node/edge: <code>root</code>, <code>confidence</code>, <code>evidence[]</code>, <code>how</code>,
<code>where</code>, <code>when</code>, <code>source_rev</code>. Leaf interval of truth (D24): <code>valid_from</code>,
<code>valid_to</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Config</h2><a id="user-content-config" class="anchor" aria-label="Permalink: Config" href="#config"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><code>~/.config/brain/</code> (0600):</p>
<ul dir="auto">
<li><code>search.env</code> — real <code>BRAIN_SEARCH_URL/USER/PASS</code> from <code>~/.config/ops/npm-bot.env</code></li>
<li><code>db-profiles.yml</code> — real <code>onlyoffice</code> profile (SSH tunnel <code>127.0.0.1:5433</code>,
user <code>onlyoffice</code>, db <code>onlyoffice</code>, <code>network: host</code>,
<code>password_env_file: ~/.config/ops/onlyoffice.env</code>) + example profiles.
<code>~/.config/ops/onlyoffice.env</code> is bootstrapped from the VM's
<code>/etc/onlyoffice/documentserver/local.json</code> (dbUser/dbPass), never committed.</li>
<li><code>~/.config/brain/../</code> — nothing else lives in the repo.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Tooling conventions</h2><a id="user-content-tooling-conventions" class="anchor" aria-label="Permalink: Tooling conventions" href="#tooling-conventions"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><code>bin/{subject}/{method}</code> — line 2 is a usage comment (mirrors <code>psql-yq</code>).</li>
<li>bash + golang primary (Go shebang); no Python in-tree.</li>
<li>YAML default output, <code>--json</code> for machines. Slice with mikefarah/yq.</li>
<li>Everything that touches the network / DB is read-only, throttled, cached.</li>
<li>Tests (TDD) gate every commit; <code>gh</code> + CI/CD on every push.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Open questions (v2)</h2><a id="user-content-open-questions-v2" class="anchor" aria-label="Permalink: Open questions (v2)" href="#open-questions-v2"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>OQ1: <strong>in</strong> — D16 adjudication: <code>temporal_freshness</code> then <code>authority_pairing</code>.
Unresolved 2v2 stays hypothesis. <a href="https://git.produktor.io/eSlider/2dph/issues/29" rel="nofollow">#29</a>.</li>
<li>OQ2: OCR — <strong>in</strong>. <code>pdftotext -layout</code> first; scans <code>pdftoppm</code> + tesseract
<code>eng+deu</code> (<code>bin/mail/ocr.go</code>, <code>internal/ocr</code>). No gocv, no gosseract CGO
(D21 Zig owns Ladybug CGO). Optional <code>OCR_ENGINE=paddle</code> / compose profile
<code>ocr-paddle</code>. Docling left the default path. When <code>pdftotext</code> yields no text
layer (scanned / export-locked / oversized), <strong>Ghostscript normalizes the PDF
first</strong> (<code>internal/ocr.NormalizePDF</code>, gs <code>pdfwrite</code> → <code>var/tmp</code>, original
preserved, artifact ≤ original): strips export-protection + shrinks so the
pdftotext fast path / tesseract fallback see a clean PDF. Clean PDFs skip gs
(happy path stays fast). <a href="https://git.produktor.io/eSlider/2dph/issues/6" rel="nofollow">#6</a>,
<a href="https://git.produktor.io/eSlider/2dph/issues/102" rel="nofollow">#102</a>.</li>
<li>OQ3: <strong>in</strong> — duckdb-go (<code>pkg/duckdb</code>, <code>bin/jsonl/stats.go</code>) for
quantiles / JSONL count. Not a second graph. <a href="https://git.produktor.io/eSlider/2dph/issues/30" rel="nofollow">#30</a>.</li>
<li>OQ4: YAML-first storage for leafs — deferred: JSON is ~10x faster to
serialize and unambiguous; YAML only where humans edit files.</li>
<li>OQ5: <strong>in</strong> — fact <code>valid_from</code> / <code>valid_to</code> + <code>--as-of</code> / MCP <code>as_of</code> (D24).
Not D16 <code>temporal_freshness</code>. <a href="https://git.produktor.io/eSlider/2dph/issues/36" rel="nofollow">#36</a>.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Mail pipeline (done)</h2><a id="user-content-mail-pipeline-done" class="anchor" aria-label="Permalink: Mail pipeline (done)" href="#mail-pipeline-done"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li><code>bin/mail/sync.go</code> (Go, 8 workers) — paginated Gmail / OnlyOffice / M365
Graph download. Gmail attachments key off <code>body.attachmentId</code>, not MIME
<code>partId</code>. M365 uses client-credentials + delta link (commit after success).
M365 inline attachments are kept (#312): stored in <code>attachments/</code> under
<code>inline-&lt;hash8&gt;-&lt;name&gt;</code>, flagged <code>isInline</code>/<code>contentId</code> in <code>message.json</code>.</li>
<li><code>bin/mail/import.go --from-raw</code> — message.json → message.md; PDFs via
<code>pdftotext -layout</code> (~15ms); textless/scanned PDFs <code>pdftoppm</code> + tesseract
<code>eng+deu</code>. PDFs without a readable text layer are first normalized with
Ghostscript (<code>internal/ocr.NormalizePDF</code>, gs <code>pdfwrite</code>; original preserved,
gs artifact in <code>var/tmp</code>, removed after extraction). ICS sidecars
Latin-1→UTF-8 normalized.</li>
<li><code>bin/brain/index.go --rebuild</code> — fresh rebuild (repo corpus + mail) because ladybug
corrupts its WAL on bulk-insert into an already-indexed DB. Conversion and
indexing stay separate for crash safety.</li>
<li>Compose <code>mail-sync</code> / <code>scripts/stack/start-mail-sync</code> — ETL loop (default
<code>onlyoffice,gmail</code>, 300s): download (existing mailsync binary) → bounded
runner pipeline import (#98) with per-run stats YAML; full rebuild only if
<code>MAIL_SYNC_INDEX=1</code>. Bot digests (ai-bot) and case wrappers reuse sync/OAuth;
they do not replace the corpus path.</li>
<li>Result: 17,835 messages → 28,918 info leafs, FTS + HNSW healthy, searchable
via <code>bin/brain/search.go</code>.</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">CI/CD pipeline (D15)</h2><a id="user-content-cicd-pipeline-d15" class="anchor" aria-label="Permalink: CI/CD pipeline (D15)" href="#cicd-pipeline-d15"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><code>.github/workflows/ci.yml</code>:</p>
<ol dir="auto">
<li>go vet + go test ./... (root module; packages without ladybug cgo)</li>
<li><code>go test ./internal/brain/rank</code> (cgo-free ranking + flag parser)</li>
<li><code>bin/facts/audit self</code> (lexicon internal consistency; <code>bin/facts/audit.go</code> is the D14 wrapper)</li>
<li><code>bin/brain/eval.go</code> via Zig (recall@5 ≥ 0.95) — the CI SoT.</li>
<li><code>bin/cgo/zig go build -tags system_ladybug</code> (compile search with zig cc; fetches pinned zig+libs).</li>
</ol>
<p dir="auto">Feedback loop: every commit → PR → CI → green/gate → merge. Same discipline as
<code>db/tech-poc</code>: contract first where there is an OpenAPI/message shape.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Execution order</h2><a id="user-content-execution-order" class="anchor" aria-label="Permalink: Execution order" href="#execution-order"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>scaffold repo (:done after this file + AGENTS.md + .gitignore + ci)</li>
<li>gh repo create eSlider/2dph --private + initial commit + CI</li>
<li>vendored skill integration (web-search, postgres, brain, diataxis-docs) — no remote links</li>
<li>schema + tools with TDD (kb + md + facts + brain)</li>
<li>~/.config/brain config</li>
<li>corpus extraction (facts/info) — <strong>in</strong>: <a href="https://git.produktor.io/eSlider/2dph/issues/18" rel="nofollow">#18</a></li>
<li>verify: web-search smoke, onlyoffice pg, md-db round-trip, eval, audit</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Gap to v1 (epic #16)</h2><a id="user-content-gap-to-v1-epic-16" class="anchor" aria-label="Permalink: Gap to v1 (epic #16)" href="#gap-to-v1-epic-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Remaining: none for epic #16 (v1). Board:
<a href="https://git.produktor.io/eSlider/2dph/issues/16" rel="nofollow">epic #16</a>,
milestone <a href="https://git.produktor.io/eSlider/2dph/milestone/12" rel="nofollow">v1 detective brain</a>.
Narrative: <a href="/eSlider/2dph/blob/main/docs/roadmap.md">docs/roadmap.md</a>.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Order</th>
<th>Issue</th>
<th>Gap</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="https://git.produktor.io/eSlider/2dph/issues/14" rel="nofollow">#14</a></td>
<td><strong>in</strong> — <code>bin/brain/add.go</code> / <code>POST /ingest</code> write facts+info without deleting <code>kb.lbug</code>. Bulk corpus still <code>--rebuild</code>. Leftover Python (mail/facts) is not the living-graph blocker.</td>
</tr>
<tr>
<td>2</td>
<td><a href="https://git.produktor.io/eSlider/2dph/issues/17" rel="nofollow">#17</a></td>
<td><strong>in</strong> — <code>--hop N</code> walks <code>FROM_FILE</code> → <code>HAS_VERSION</code> → <code>AUTHORED</code> (max 3).</td>
</tr>
<tr>
<td>3</td>
<td><a href="https://git.produktor.io/eSlider/2dph/issues/18" rel="nofollow">#18</a></td>
<td><strong>in</strong> — <code>--with-facts</code> / <code>--facts-json</code> land <code>root=facts</code>; <code>--with-chats</code> indexes <code>var/corpus/chats/md</code>. WhatsApp sync is in v1 via <a href="https://git.produktor.io/eSlider/2dph/pulls/87" rel="nofollow">#87</a>.</td>
</tr>
<tr>
<td>4</td>
<td><a href="https://git.produktor.io/eSlider/2dph/issues/15" rel="nofollow">#15</a></td>
<td><strong>in</strong> — lever/loop documented (<code>search</code> → <code>get</code> → <code>audit</code>).</td>
</tr>
<tr>
<td>5</td>
<td><a href="https://git.produktor.io/eSlider/2dph/issues/19" rel="nofollow">#19</a></td>
<td><strong>in</strong> — CI recall SoT is <code>bin/brain/eval.go</code> via Zig.</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Does <strong>not</strong> block epic close: OQ4. OCR <a href="https://git.produktor.io/eSlider/2dph/issues/6" rel="nofollow">#6</a>, OQ1 <a href="https://git.produktor.io/eSlider/2dph/issues/29" rel="nofollow">#29</a>, OQ3 <a href="https://git.produktor.io/eSlider/2dph/issues/30" rel="nofollow">#30</a> are <strong>in</strong>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)</h2><a id="user-content-2026-09-03--l-93-формальные-проверки-url-identitycontradictionexcluded-middlesufficient-reason-gitea-232-epic-229" class="anchor" aria-label="Permalink: 2026-09-03 — L-9.3: формальные проверки URL: identity/contradiction/excluded-middle/sufficient-reason (gitea #232, epic #229)" href="#2026-09-03--l-93-формальные-проверки-url-identitycontradictionexcluded-middlesufficient-reason-gitea-232-epic-229"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: перевести эмпирические URL-рецепты (#56) в машинные проверки законов
Vinogradov на уровне URL: identity (канонический URL — одно написание),
contradiction (¬(P∧¬P) на одном URL), excluded middle (нечёткие claim → не
FACT, а open_questions), sufficient reason (verdict accept только на
FACT-/OPEN- premises). Ядро — cgo-free <code>internal/facts</code> (по образцу
Adjudicate/CheckFactRow), CLI — подкоманда <code>bin/facts/audit.go formal</code>
(JSON facts/cards на stdin → JSON-флаги, exit 0/1).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>facts.CanonicalURL</code> — локальная канонизация URL по конвенции gator G-8.1 (lowercase scheme/host, default-port, utm_*/ref/gh_jid/token, сортировка query, без фрагмента/trailing slash); gator-модуль не импортируется</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/facts/formal.go</code>: <code>URLFact</code> (id/url/claim/attr/neg/conf/from/to) + <code>CheckURLIdentity</code> (merge/flag weaken), <code>CheckURLContradiction</code> (reject; только confirmed, пересечение D24), <code>CheckExcludedMiddle</code> (weaken → open_questions), <code>CheckSufficientReason</code> (accept без FACT-/OPEN- premises → reject), <code>CheckFormal</code> (агрегат, детерминизм)</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>bin/facts/audit.go formal</code> (shebang, hand-formatted): JSON {facts,cards} на stdin → {mode,ok,problems}; пример acceptance «вакансия активна» vs «вакансия closed» → contradiction verdict=reject</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>canonical_test.go</code> (кросс-канал/utm/детерминизм/нормализация/ошибки) + <code>formal_test.go</code> (identity merge, contradiction acceptance + не-противоречия: same polarity/разный attr/weak/disjoint D24, excluded-middle fuzzy→OPEN, sufficient-reason accept premises)</td>
<td>done — <code>go test -race ./internal/facts</code> green</td>
</tr>
<tr>
<td>Совместимость с #56/#231: <code>go test ./internal/facts</code> + <code>bin/facts/audit.go self</code> зелёные (CI-гейт)</td>
<td>done</td>
</tr>
<tr>
<td>Документация: docs/facts/audit-recipes.md §Формальные проверки URL (L-9.3 #232) + PLAN.md</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test -race ./internal/facts/</code> green, <code>go vet ./internal/facts/</code> clean,
<code>./bin/facts/audit.go self</code> ok. Branch <code>feat/facts-formal-checks#232</code> off <code>main</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)</h2><a id="user-content-2026-09-02--l-94-git-commits--commitpersonauthored-граф-gitea-233-epic-229" class="anchor" aria-label="Permalink: 2026-09-02 — L-9.4: git-commits → Commit/Person/AUTHORED граф (gitea #233, epic #229)" href="#2026-09-02--l-94-git-commits--commitpersonauthored-граф-gitea-233-epic-229"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: заполнить Commit/Person/AUTHORED (InitSchema write.go:130-133 уже создаёт
таблицы, писателя не было) из git-истории репо git.produktor.io — источник
фактов «кто над чем работал, когда» для аудита L-9 и оси проекта сети #234.
Дизайн: <a href="/eSlider/2dph/blob/main/docs/brain/graph-git-commits.md">docs/brain/graph-git-commits.md</a>.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Канон git-коммита: <code>(repo, sha)</code>, <code>Commit.id = &lt;repo&gt;:&lt;sha&gt;</code> (repo = RepoName origin-remote); чтение — <code>gitlog.Log</code> (go-git, тот же канон, что premises-корпус: merge пропущены, автор = Author, дата RFC3339)</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/gitgraph</code> (cgo-free): Resolve (root-скан/--repo), ReadAll, ToInputs (email lowercase — Person.id канон mail D-1 #257, dedup, сортировка по дате ASC), ComputeStats, WeakSubject</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/brain/commitplan.go</code> (cgo-free): CommitInput, MERGE-запросы commit/AUTHORED, commitAuthor (Person.id=email); <code>gitcommit.go</code> (cgo): UpsertCommit/UpsertCommits (транзакция, идемпотентно)</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>bin/git/graph.go</code>: dry-run/--commit, --repo/--root/--since/--limit, --skip-existing, live-holder guard, батчи; отчёт before→after Commit/Person/AUTHORED</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>bin/git/facts.go</code> (read-only): факт-карточки «кто работал над repo когда» + audit card (claim/premises/inference/gaps/verdict; слабый subject → weaken+OPEN)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: юнит cgo-free (commitplan + gitgraph: канон/маппинг/dedup/Resolve/ReadAll/WeakSubject/GroupFacts/BuildFacts) + cgo на живой Ladybug (temp DB): запись/перечитывание, повтор = 0 дублей, Person mail↔git сопряжение, сосуществование с Leaf/Message</td>
<td>done</td>
</tr>
<tr>
<td>Документация: docs/brain/graph-git-commits.md + шаблон audit card в docs/facts/audit-recipes.md</td>
<td>done</td>
</tr>
<tr>
<td>Live-пилот: 2dph + gator dry-run → --commit в kb.lbug (бэкап, live-holder guard), idempotency-повтор, git-facts спот-чек</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test ./...</code> + <code>go vet ./...</code> green (CI-режим);
<code>bin/cgo/zig go test -tags system_ladybug ./internal/brain/</code> green (весь пакет);
<code>bin/cgo/zig go vet -tags system_ladybug ./internal/brain/</code> green.
Branch <code>feat/git-commits#233</code> off <code>main</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)</h2><a id="user-content-2026-09-02--l-95-сеть-связей-кто-с-кем-и-через-кого-gitea-234-epic-229" class="anchor" aria-label="Permalink: 2026-09-02 — L-9.5: сеть связей «кто с кем и через кого» (gitea #234, epic #229)" href="#2026-09-02--l-95-сеть-связей-кто-с-кем-и-через-кого-gitea-234-epic-229"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: инструмент сети поверх готового графа (mail D-1 #257 + git L-9.4 #233):
Person↔Person по общим письмам/тредам/получателям (SENT/TO/CC/BCC/REPLY_TO) и
общим проектам (AUTHORED Commit.repo), с premises (Message.id/Commit.id) и
verdict по audit-картам (accept/weaken); экспорт accept в CRM (ADR-0012 п.4).
Read-only: ничего не пишет, схему не меняет. Дизайн:
<a href="/eSlider/2dph/blob/main/docs/brain/graph-network.md">docs/brain/graph-network.md</a>.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/network</code> (cgo-free): BuildLinks — деривации mail (прямой контакт sender↔recipient, вес TO/CC/BCC, общий получатель, REPLY_TO-диалог, треды) + git (общий проект по AUTHORED Commit.repo), premises, период, verdict accept/weaken+OPEN, ранжирование</td>
<td>done</td>
</tr>
<tr>
<td>Read-only запросы графа <code>internal/network/query.go</code> (cgo): LoadRows — Message/Person/рёбра SENT/TO/CC/BCC/REPLY_TO + Commit/AUTHORED → строки сети</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>bin/network/network.go</code> (read-only): <code>--person</code> (обязателен), <code>--project</code>, <code>--since/--until</code>, <code>--limit</code>, <code>--json</code>, <code>--accept-only</code> (CRM-экспорт YAML)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: юнит cgo-free (деривации/вес/вердикты/ранжирование/premises на synthetic Alice/Bob/Carol) + cgo на живой Ladybug (temp DB): LoadRows→BuildLinks end-to-end</td>
<td>done</td>
</tr>
<tr>
<td>Live-пилот: <a href="mailto:eslider@gmail.com">eslider@gmail.com</a> / <a href="mailto:andriy.oblivantsev@wheregroup.com">andriy.oblivantsev@wheregroup.com</a> на живой kb.lbug (read-only) — топ связей, premises, CRM-экспорт accept</td>
<td>done</td>
</tr>
<tr>
<td>Документация: docs/brain/graph-network.md + PLAN.md</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test ./...</code> + <code>go vet ./...</code> green (CI-режим);
<code>bin/cgo/zig go test -tags system_ladybug ./internal/network/ ./internal/brain/</code>
green (cgo). Branch <code>feat/network#234</code> off <code>main</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)</h2><a id="user-content-2026-09-02--d-12-messageperson-граф-схема--write-путь-gitea-259-epic-257" class="anchor" aria-label="Permalink: 2026-09-02 — D-1.2: Message/Person граф-схема + write-путь (gitea #259, epic #257)" href="#2026-09-02--d-12-messageperson-граф-схема--write-путь-gitea-259-epic-257"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: перенести conversation-канон (<a href="https://git.produktor.io/eSlider/2dph/issues/99" rel="nofollow">#99</a>,
<code>internal/canon</code>) в рабочую Ladybug-схему kb.lbug: узел <strong>Message</strong> + рёбра
<strong>SENT/TO/CC/BCC/REPLY_TO</strong> рядом с существующими Leaf/Person; идемпотентная
запись (MERGE). Дизайн: <a href="/eSlider/2dph/blob/main/docs/brain/graph-gator-mail.md">docs/brain/graph-gator-mail.md</a>
(D-1.1 #258). По итогу почта в 2dph живёт как граф сущностей, а не Leaf-текст
из сырья.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>InitSchema</code> аддитивно (<code>IF NOT EXISTS</code>): <code>Message</code> node (id/thread_id/folder/subject/sent_at/gator_ref/body, PK id) + rel <code>SENT (Person→Message)</code>, <code>TO/CC/BCC (Message→Person)</code>, <code>REPLY_TO (Message→Message)</code>; Person/Leaf/Commit не тронуты</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/brain/graphplan.go</code> (cgo-free): <code>MessageInput</code> (canon.Message + folder/subject/gator_ref), <code>planGraph</code> — уникальные Person (From→To→CC→BCC, name первого вхождения), рёбра = <code>canon.Edges()</code> минус PART_OF/само-REPLY_TO; генерация MERGE-запросов (person/message/edge), <code>edgeSchema</code></td>
<td>done</td>
</tr>
<tr>
<td><code>internal/brain/graph.go</code> (cgo): <code>UpsertMessage</code>/<code>UpsertMessages</code> — пачка в одной транзакции, MERGE по message_id/email; Person name-мердж «последний sync побеждает» (SET в MERGE); gator_ref на Message</td>
<td>done</td>
</tr>
<tr>
<td>TDD: юнит cgo-free (план persons/edges, детерминизм, формы MERGE-запросов, sentAtText); cgo-тесты на живой Ladybug (temp DB): запись+перечитывание, повторный прогон = 0 дублей, name-мердж, сосуществование с Leaf + EnsureIndexes</td>
<td>done — 8 юнит + 4 cgo зелёные</td>
</tr>
<tr>
<td>Документация: §8 «Реализация D-1.2» в дизайн-доке (схема + write-путь)</td>
<td>done</td>
</tr>
<tr>
<td>Импорт gator kind=mail → граф (wheregroup 3984)</td>
<td>#260 (следующий)</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>bin/cgo/zig go test -race -tags system_ladybug ./internal/brain/</code> green
(весь пакет, ~6 мин); <code>go vet ./...</code> + <code>go test ./...</code> green (CI-режим);
<code>go test ./internal/canon/</code> — 14 тестов green (canon #99 не изменён).
Branch <code>feat/graph-schema#259</code> off <code>main</code> (cc40235).</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-18 — Go-only write path sync (gitea #41)</h2><a id="user-content-2026-08-18--go-only-write-path-sync-gitea-41" class="anchor" aria-label="Permalink: 2026-08-18 — Go-only write path sync (gitea #41)" href="#2026-08-18--go-only-write-path-sync-gitea-41"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: consolidate the most advanced version here and in sync with GitHub main.
Sources: GitHub main <code>b1e6953</code> + arc-1 <code>/mnt/8TB/projects/ai/2dph</code> Go-write WIP +
edelweiss <code>/home/devops/projects/2dph</code> M365 WIP. Prefer Go; Python only for A/B.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>sync issues via tea (gitea #41 assigned)</td>
<td>done</td>
</tr>
<tr>
<td>Phase 1: branch <code>feat/brain-go-writepath</code> + arc-1/edelweiss WIP</td>
<td>done</td>
</tr>
<tr>
<td>Phase 2: finish Go write path (write.go/corpus.go/index/add/mailconv) TDD</td>
<td>done</td>
</tr>
<tr>
<td>yaml-seed secret/noise filter; compose/CI → Go (no Python INSTALL)</td>
<td>done</td>
</tr>
<tr>
<td>Phase 3: PR → CI green → merge GitHub main; docs (D6 reversal)</td>
<td>done — PR #42, merged <code>2cea0e7</code></td>
</tr>
<tr>
<td>Phase 4: go-ollama + go-xls PR/merge; verify go-env/go-hocr/go-onlyoffice</td>
<td>done — PR #4 (<code>325c4e2</code>), PR #1 (<code>165d25d</code>)</td>
</tr>
<tr>
<td>Phase 5: deploy brain :8630 + wire MCP into opencode.json</td>
<td>done — brain live (7754 leafs), MCP wired; PR #43 merged <code>a1880de</code></td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Note: CI found + fixed 3 real bugs in the port (modeldir HF-cache default +
snapshot weight selection, secret/noise filter false positives).</p>
<p dir="auto">Deploy findings (PR #43): fts-extension install needs writable+execable HOME
(<code>/data</code>, not noexec <code>/tmp</code>); serve must bind <code>KB_HOST=0.0.0.0</code> for compose
publishing; corpus walk must skip permission-denied entries; leaf text needs
UTF-8 sanitization before FTS.</p>
<p dir="auto">Decisions (2026-08-18): exclude arc-1 junk (external/, docs/chats/, bin/chats.bin,
qa/load_test_*.py); Python fully removed (2026-08-19); M365 WIP same PR;
go-ollama+go-xls now, rest verify.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — Typed config (gitea #90, epic #88)</h2><a id="user-content-2026-08-22--typed-config-gitea-90-epic-88" class="anchor" aria-label="Permalink: 2026-08-22 — Typed config (gitea #90, epic #88)" href="#2026-08-22--typed-config-gitea-90-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: replace ad-hoc <code>os.Getenv</code> in the service layer with one typed config
loaded by a stack, keeping legacy env names working. Dep
<code>github.com/eslider/go-config@v0.4.0</code>; new <code>internal/config</code> (<code>config.go</code>,
<code>load.go</code>, <code>yaml.go</code>, <code>env.go</code>) — TDD offline fixtures, no network/db.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>internal/config load stack + merge + lower+alnum + legacy mapping (TDD)</td>
<td>done — tests green</td>
</tr>
<tr>
<td>wire pkg/httpapi, internal/brain, bin/brain/{serve,index,watch}, internal/mailsync, internal/websearch</td>
<td>done — no ad-hoc env reads in those packages</td>
</tr>
<tr>
<td>committed <code>etc/brain/config.yml</code> template + gitignore <code>config.local.yml</code></td>
<td>done</td>
</tr>
<tr>
<td>legacy env map</td>
<td>KB_ROOT→Root, KB_PORT→Port, KB_HOST→Host, KB_WORKERS→Workers, KB_PPROF→Pprof, KB_SEARCH_CMD→SearchCmd, KBSEARCH_PORT→SearchDaemonPort, KBSEARCH_NO_DAEMON→SearchNoDaemon, KBSEARCH_MODEL→Model, HF_HOME→HFHome, KB_BUFFER_POOL→BufferPool, KBTEST_EPS→Eps, KB_INDEX_ALLOW_LIVE→IndexAllowLive, KB_WATCH_INTERVAL→WatchInterval, KB_WATCH_DIRS→WatchDirs, OO_CLI→OOCLI, BRAIN_SEARCH_{URL,USER,PASS,CACHE,ENV}→Search.*</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Non-goals kept: graph/search semantics unchanged. Pending: OnlyOffice CLI
config wiring from <code>bin/mail/*</code> (out of #90 scope) — <code>internal/mailsync.SetOOCLI</code>
is ready for it.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)</h2><a id="user-content-2026-08-22--mime-parser-swap-in-mailconv-gitea-95-epic-88" class="anchor" aria-label="Permalink: 2026-08-22 — MIME parser swap in mailconv (gitea #95, epic #88)" href="#2026-08-22--mime-parser-swap-in-mailconv-gitea-95-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: replace <code>jhillyerd/enmime/v2</code> with <code>emersion/go-message</code> in
<code>internal/mailconv</code> (parser-equivalent swap, no behaviour change to graph/search).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>fixtures: plain / alternative / mixed / nested(alternative+mixed) / charset(iso-8859-1 QP) <code>.eml</code> in <code>internal/mailconv/testdata/</code></td>
<td>done</td>
</tr>
<tr>
<td><code>parseEML</code> via <code>message.Read</code> + <code>entity.Walk()</code> recursion; text/plain→TextBody, text/html→HTMLBody, attachment leaves by filename/disposition</td>
<td>done</td>
</tr>
<tr>
<td>charset via <code>go-message/charset</code> (x/text ianaindex/htmlindex) — blank import</td>
<td>done</td>
</tr>
<tr>
<td>public API stable: <code>FromEML</code>/<code>FromRaw</code>/<code>ConvertAttachment</code>/<code>Message</code>/<code>Attachment</code> unchanged; <code>writeMessageJSON</code>/<code>writeEMLAttachments</code> internal</td>
<td>done</td>
</tr>
<tr>
<td><code>go mod tidy</code> → enmime absent from go.mod</td>
<td>done</td>
</tr>
<tr>
<td><code>go build ./internal/... ./pkg/...</code> + <code>go test -race ./internal/mailconv/... ./pkg/...</code> green</td>
<td>done</td>
</tr>
<tr>
<td>-benchmem before(env v2.4.1)/after(emersion v0.18.2): ~6–9× faster ns/op, B/op ≈ half, allocs ≈ 40–55% lower</td>
<td>done — bench in <code>eml_test.go</code></td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Branch <code>feat/mime-emersion#95</code>: c8f7120 (test) → 7015ffc (feat) → 9b4425d (chore/deps).</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)</h2><a id="user-content-2026-08-22--mailsync-v1-мелкие-фиксы-gitea-111-epic-88" class="anchor" aria-label="Permalink: 2026-08-22 — mailsync v1 мелкие фиксы (gitea #111, epic #88)" href="#2026-08-22--mailsync-v1-мелкие-фиксы-gitea-111-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: small defects found porting mailsync v1 — <code>mailconv.FromEML</code> folder
extraction and the Dockerfile <code>pkg/</code> copy.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>test (red): <code>TestFromEMLFolderNestedLayout</code> — <code>&lt;root&gt;/&lt;folder&gt;/&lt;id&gt;/&lt;id&gt;.eml</code> asserts folder=<code>&lt;folder&gt;</code> via <code>message.json</code> + <code>message.md</code></td>
<td>done</td>
</tr>
<tr>
<td><code>emlFolder(root,p)</code>: parent of the <code>&lt;id&gt;</code> dir (grandparent of <code>.eml</code>) for the nested layout; flat <code>&lt;root&gt;/*.eml</code> keeps immediate dir; applied in <code>FromEML</code> + <code>writeMessageJSON</code></td>
<td>done</td>
</tr>
<tr>
<td>Dockerfile <code>pkg/</code> copy: release/v1 root <code>Dockerfile</code> already <code>COPY . .</code> (covers <code>pkg/cli</code> for <code>internal/ocr</code>); no <code>deploy/mail-watcher.Dockerfile</code> in release/v1 (that file lives on main via #107, not an ancestor) — no change needed</td>
<td>already-fixed (verify only)</td>
</tr>
<tr>
<td><code>go test ./internal/mailconv/... ./internal/mailsync/... ./internal/ocr/...</code> + <code>./internal/...</code> green; <code>gofmt -l</code> clean; <code>go vet</code> clean</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Branch <code>fix/mailsync-v1#111</code> → PR into <code>release/v1</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)</h2><a id="user-content-2026-08-23--pr--merge-featimap-mailsync-v1--releasev1-gitea-112-epic-88" class="anchor" aria-label="Permalink: 2026-08-23 — PR + merge feat/imap-mailsync-v1 → release/v1 (gitea #112, epic #88)" href="#2026-08-23--pr--merge-featimap-mailsync-v1--releasev1-gitea-112-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: land the v1 generic IMAP source (from #106) that was never merged:
<code>internal/mailsync/{imap,watch}.go</code> + offline tests + CLI/flags wiring +
<code>bin/mail/watch</code> entrypoint + <code>deploy/mail-watcher.Dockerfile</code>.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>delta audit vs release/v1 (756f514): missing = imap source files, watch loop, CLI flags (<code>--once/--brain/--ocr/--local</code>), <code>IMAP_*</code> env parsing, <code>SyncConfig.IMAP</code>, <code>bin/mail/watch</code>, <code>deploy/mail-watcher.Dockerfile</code>, <code>go-imap/v2</code>+<code>go-sasl</code> deps</td>
<td>done</td>
</tr>
<tr>
<td>overlap check: brain write-path fixes (<code>internal/brain/*</code>, <code>mailconv.FromEML</code>) already on release/v1 via #109/#110/#111 — NOT re-ported; old <code>gmail.go</code>/<code>m365.go</code>/<code>onlyoffice.go</code> untouched and compiling</td>
<td>done</td>
</tr>
<tr>
<td>port: <code>internal/mailsync/imap.go</code> + <code>imap_test.go</code> + <code>watch.go</code>, cli.go/sync.go wiring, <code>bin/mail/watch/main.go</code>, Dockerfile, <code>go.mod</code>/<code>go.sum</code></td>
<td>done</td>
</tr>
<tr>
<td>source selection: <code>--source imap</code> → <code>IMAPEnv(envVars)</code> → <code>SyncConfig.IMAP</code> → <code>newIMAPSources</code>; <code>cfg.Raw=true</code> (always <code>.eml</code>); readEnv honours <code>IMAP_*</code></td>
<td>done</td>
</tr>
<tr>
<td>tests (offline, synthetic Alice/Bob): <code>SanitizeFolder</code>, <code>IMAPEnv</code>, <code>parseIMAPMessage</code> (emersion parser), <code>hasNoSelect</code>; no network</td>
<td>done</td>
</tr>
<tr>
<td><code>go test ./internal/...</code> + <code>./internal/mailsync/...</code> green; <code>go vet</code> clean; <code>gofmt -l</code> clean (shebang-protected files excluded)</td>
<td>done</td>
</tr>
<tr>
<td>branch <code>merge/imap-mailsync-v1#112</code> off <code>origin/release/v1</code> → PR into <code>release/v1</code>, CI green</td>
<td>done (open, not merged)</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Branch <code>merge/imap-mailsync-v1#112</code> → PR into <code>release/v1</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)</h2><a id="user-content-2026-08-23--reasoner-client--api-client-canon-gitea-93-epic-88" class="anchor" aria-label="Permalink: 2026-08-23 — reasoner client → api-client canon (gitea #93, epic #88)" href="#2026-08-23--reasoner-client--api-client-canon-gitea-93-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: apply <code>skills/api-client/SKILL.md</code> (go-ollama canon) to
<code>internal/reasoner/client.go</code>, first candidate. Removes the 16× <code>map[string]any</code>
(typed <code>Tool</code>/<code>ToolSchema</code>).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>test: offline httptest typed client — chat round-trip, tool-call, error/status, ctx cancel, NDJSON doStream</td>
<td>done — <code>-race</code> green</td>
</tr>
<tr>
<td>feat: <code>Client{hc,cfg}</code> + <code>New(cfg)</code>, <code>doJSON</code>/<code>doStream</code> core, typed <code>ChatRequest</code>/<code>ChatResponse</code>, ctx-first</td>
<td>done</td>
</tr>
<tr>
<td>config: <code>reasoner.Config</code> + <code>LoadEnv()</code> accessor; bench.go reads <code>REASONER_BASE_URL</code> from it</td>
<td>done</td>
</tr>
<tr>
<td>bench.go: typed client, <code>ctx</code>; bake-off behaviour preserved</td>
<td>done</td>
</tr>
<tr>
<td><code>map[string]any</code> in <code>internal/reasoner/client.go</code> → 0</td>
<td>done</td>
</tr>
<tr>
<td>PLAN.md #93 status</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Notes: rebased onto <code>release/v1</code> <code>36f5ec4</code> (after #90 landed). <code>internal/config</code>
carries no reasoner fields, so a minimal <code>reasoner.LoadEnv()</code> accessor (env →
<code>Config</code>) is kept; wiring <code>REASONER_BASE_URL</code> into <code>internal/config.Config</code> is
deferred. Bench shebang is <code>gofmt</code>-protected (not reformatted).</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)</h2><a id="user-content-2026-08-22--go-config-wave-2-tools--tests-gitea-91-epic-88" class="anchor" aria-label="Permalink: 2026-08-22 — go-config wave 2: tools + tests (gitea #91, epic #88)" href="#2026-08-22--go-config-wave-2-tools--tests-gitea-91-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: extend <code>internal/config</code> to the remaining tools/packages, add a <code>reasoner</code>
section and retire the ad-hoc <code>os.Getenv</code> config reads (KB_<em>, BRAIN_SEARCH_</em>,
REASONER_*, HF_HOME, model, port/workers…).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>config.ReasonerConfig{BaseURL,Model,Device}</code> + <code>Config.Reasoner</code> section + defaults</td>
<td>done</td>
</tr>
<tr>
<td>legacy map: REASONER_BASE_URL→Reasoner.BaseURL, REASONER_MODEL→Reasoner.Model, REASONER_DEVICE→Reasoner.Device</td>
<td>done</td>
</tr>
<tr>
<td>reasoner client config from typed stack: <code>reasoner.Configure(cfg)</code> + <code>FromTyped</code>; <code>LoadEnv()</code> reads <code>active</code></td>
<td>done — <code>bin/reasoner/bench.go</code> loads <code>config.Load</code> + <code>Configure</code>; client tests green</td>
</tr>
<tr>
<td><code>bin/web/search.go</code>: BRAIN_SEARCH_CACHE/ENV → <code>cfg.Search.Cache/Env</code> (HOME fallback kept)</td>
<td>done</td>
</tr>
<tr>
<td><code>bin/brain/model.go</code>: HF_HOME/KB_ROOT → <code>cfg.HFHome/cfg.Root</code></td>
<td>done</td>
</tr>
<tr>
<td><code>etc/brain/config.yml</code> template gains <code>reasoner:</code> section</td>
<td>done</td>
</tr>
<tr>
<td>TDD fixtures: <code>reasoner</code> deep-merge in <code>testdata/basic</code>, legacy + defaults tests, <code>isolateEnv</code> covers REASONER_*</td>
<td>done — <code>go test -race ./internal/... ./pkg/...</code> green</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Notes: <code>internal/chat/paths.go</code> KB_ROOT + <code>pkg/repo/exec.go</code> KB_ROOT left as
ad-hoc env reads — chat sources are secret-heavy (no config accessor yet) and
<code>pkg/</code> must not import <code>internal</code> (overlaps #92 <code>pkg/utils</code>). Bench/web shebangs
are <code>gofmt</code>-protected (not reformatted).</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)</h2><a id="user-content-2026-08-22--pkgutils--static-typing-hotspots-gitea-92-epic-88" class="anchor" aria-label="Permalink: 2026-08-22 — pkg/utils + static-typing hotspots (gitea #92, epic #88)" href="#2026-08-22--pkgutils--static-typing-hotspots-gitea-92-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: promote duplicated helpers into public <code>pkg/utils</code> with static typing and
kill the <code>map[string]any</code> / <code>interface{}</code> HTTP and root-resolution hotspots.
<code>pkg/utils</code> stays internal-free (D29, primitives only).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pkg/utils/root.go</code> <code>Root()</code> — KB_ROOT or walk-up <code>.git</code>/<code>var</code>, single source of truth; <code>pkg/repo.Root()</code> and <code>internal/chat.Root()</code> delegate</td>
<td>done</td>
</tr>
<tr>
<td><code>pkg/utils/json.go</code> <code>DoJSON</code>/<code>GetJSON</code> — typed request + 2xx check + decode, <code>opts</code> for auth; wired into <code>internal/reasoner.Client.doJSON</code>, <code>internal/websearch.Fetch</code>, <code>internal/chat.cdpTabs</code></td>
<td>done</td>
</tr>
<tr>
<td><code>pkg/utils/str.go</code> <code>Snippet</code>/<code>Or</code> — <code>reasoner.snippet</code>, <code>chat.orDefault</code> removed</td>
<td>done</td>
</tr>
<tr>
<td>TDD fixtures: <code>pkg/utils/{root,json,str}_test.go</code> offline (httptest, t.TempDir)</td>
<td>done — <code>go test -race ./pkg/... ./internal/...</code> green, <code>go vet</code> clean, CGO ladybug build green</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Notes: <code>pkg/httpapi</code> write-side (<code>writeJSON</code>/<code>writeRaw</code>) is a different direction
(server response, not client decode) and was left as-is. Remaining
<code>map[string]any</code>/<code>interface{}</code> decode sites in <code>internal/brain</code>, <code>internal/chat</code>
(MCP/JSONL) and <code>internal/config</code> are domain-shaped and out of scope for this
pass.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)</h2><a id="user-content-2026-08-22--ladybug-write-path-hardening--soak-gitea-109-epic-88" class="anchor" aria-label="Permalink: 2026-08-22 — Ladybug write-path hardening + soak (gitea #109, epic #88)" href="#2026-08-22--ladybug-write-path-hardening--soak-gitea-109-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: kill the silent in-process Ladybug death on <code>POST /ingest</code> (~10% of
writes, ExitCode=0, no Go panic) and prove it with a soak test.</p>
<p dir="auto">Root cause (release/v1): <code>HTTP.Ingest</code> opened a <strong>second</strong> handle on the live
<code>kb.lbug</code> (<code>OpenWritable</code>) while the serve read handle stayed open — liblbug
dies silently when the same file is double-opened inside one process. The
existing close→reopen swap (<code>refreshBrain</code>) also ran unsynchronized against
concurrent handlers: any reader mid-query could execute a statement against a
just-closed <code>*lbug.Connection</code> (C use-after-close; frame
<code>LocalNodeTable::isVisible ← HashIndex::lookupInPersistentIndex</code>). Error paths
left the serve handle stale for the process lifetime.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>brainMu sync.RWMutex</code> guards global db/conn; readers (Get/Stats/Audit, queryFTS/queryVector/attachHops, lookupLeaf/leafStats) hold RLock</td>
<td>done</td>
</tr>
<tr>
<td>Ingest serialized (<code>ingestMu</code>), holds write lock over close→write→reopen; <code>closeBrainLocked()</code> runs BEFORE <code>OpenWritable()</code> (no same-file double-open)</td>
<td>done</td>
</tr>
<tr>
<td>Restore on every exit path (<code>ingestWriteLocked</code> + deferred restore): write-stage errors no longer leave a stale/nil serve handle</td>
<td>done</td>
</tr>
<tr>
<td><code>embedIngestLeafs</code> seam — embed step extracted as package var, tests run full Ingest offline (no HF model/daemon); singleton-cache (#110) replaces only its body</td>
<td>done</td>
</tr>
<tr>
<td>Soak: <code>TestIngestSoakWritePath</code> 200 × real <code>/ingest</code> + 8 concurrent readers (Stats/Audit/Get/FTS/vector), temp KB_ROOT, offline</td>
<td>red before fix (native segfault), green after</td>
</tr>
<tr>
<td>Race proofs: <code>TestIngestSwapRaceSafe</code>, <code>TestIngestWriteRestoresServeConnOnError</code></td>
<td>green under <code>-race</code></td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verify: <code>bin/cgo/zig go test -race -tags system_ladybug ./internal/brain/</code>
green; <code>go vet</code> clean; zig build of <code>bin/brain/search.go</code> OK. Same-file
double-open stays forbidden inside one process; bulk rebuild keeps its own
process (<code>bin/brain/index.go</code>). Out of scope: corpus CLI path (single-goroutine
index process), model singleton cache (#110).</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)</h2><a id="user-content-2026-08-23--etl-реестр-обработчиков--безопасный-walker-gitea-96-epic-88" class="anchor" aria-label="Permalink: 2026-08-23 — ETL-реестр обработчиков + безопасный walker (gitea #96, epic #88)" href="#2026-08-23--etl-реестр-обработчиков--безопасный-walker-gitea-96-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: base layer for sync-ETL. A concurrency-safe registry of per-format ETL
handlers plus a hardened file walker (path traversal / symlink-escape /
depth-limit / binary-huge-file skip). Runner (#98) and source-adapters (#97)
build on these.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/etl/registry.go</code>: <code>Registry</code> (RWMutex) mapping handler key → <code>Handler{Name() string; Handle(ctx, path) error}</code>; <code>Register</code> rejects duplicate/empty keys, <code>Lookup</code>/<code>Names</code>(sorted)/<code>Len</code> — concurrent-safe</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/etl/walker.go</code>: <code>WalkFiles(root, WalkOptions{MaxDepth, MaxBytes, SkipBinary, Exts})</code> → deterministic sorted <code>[]File{Path,Rel,Size}</code>; rejects <code>..</code> traversal, never follows symlinked dirs, rejects symlink escapes outside root, honors depth limit, skips huge/binary files</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/markdown.WalkMarkdown</code> refactored to the hardened walker (Exts filter)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/etl/etl_test.go</code> — register/lookup/dup/sorted/concurrent(-race), deterministic order, traversal rejected, symlink-to-outside rejected, depth limit, large-file skip, binary skip, ext filter, missing-root error</td>
<td>done — <code>go test -race ./internal/...</code> green, <code>go vet</code> clean, gofmt clean</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Notes: scope kept to registry + walker only; pipeline rewiring is #97/#98.
<code>internal/mailconv</code> and <code>bin/*</code> walkers stay legacy until a source adapter owns
them (single-implementation rule D33) — cutover tracked in #98.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)</h2><a id="user-content-2026-08-25--source-adapters-gitea-97--bounded-etl-runner-gitea-98-epic-88" class="anchor" aria-label="Permalink: 2026-08-25 — source adapters (gitea #97) + bounded ETL runner (gitea #98, epic #88)" href="#2026-08-25--source-adapters-gitea-97--bounded-etl-runner-gitea-98-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: unified <code>Source</code> adapter layer + the bounded pipeline runner that wires
<code>Source → Decode(Registry) → Transform → Load</code> with backpressure, per-run stats
YAML and graceful shutdown.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/source</code>: <code>Source{Name(); Fetch(ctx,cursor)→[]Blob,Cursor,error}</code>, <code>Blob{ID,Kind,Path}</code>, <code>Cursor</code>; <code>Sync(ctx,src,handle,Options{StatePath})→Stats{Fetched,New,Skipped}</code> — sha256 seen-set dedup + atomic checkpoints, mid-batch resume</td>
<td>done (#97)</td>
</tr>
<tr>
<td><code>source.Disk</code>: re-lists a corpus dir of <code>.eml</code>, content-hash identity, cursor never advances (driver dedups)</td>
<td>done (#97)</td>
</tr>
<tr>
<td><code>internal/runner</code>: bounded channels between stages (backpressure), <code>errgroup.WithContext</code> first-error cancel, wg-accounted goroutines, graceful shutdown; reuses <code>source.Sync</code> + <code>etl.Registry</code></td>
<td>done</td>
</tr>
<tr>
<td>Runner <code>Report</code>: source stats + per-handler ok/failed counts, serialized as stats YAML (<code>startedAt/duration/source/handlers</code>)</td>
<td>done</td>
</tr>
<tr>
<td><code>bin/runner/run.go</code>: CLI daemon over the disk corpus; <code>mail</code> handler → <code>mailconv.FromEMLFile</code> (per-blob .eml→md); <code>--interval</code> loop + SIGINT/SIGTERM graceful shutdown</td>
<td>done</td>
</tr>
<tr>
<td>Compose <code>mail-sync</code> migrates to the runner (<code>command: ["runner","300"]</code>); <code>MAIL_SYNC_SRC/ENV/INDEX</code> semantics preserved (<code>INDEX=1</code> = full brain rebuild); download stays on the existing mailsync binary (source-adapter mapping for onlyoffice/gmail = follow-up)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/runner/runner_test.go</code> — e2e disk source smoke (stats YAML round-trip), bounded in-flight backpressure, graceful shutdown on cancel (-race), unknown-kind error; <code>internal/mailconv</code> <code>FromEMLFile</code> idempotence</td>
<td>done — <code>go test -race</code> green, <code>go vet</code> clean, <code>docker compose config</code> parses</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-22 — singleton-cache ingest model (gitea #110)</h2><a id="user-content-2026-08-22--singleton-cache-ingest-model-gitea-110" class="anchor" aria-label="Permalink: 2026-08-22 — singleton-cache ingest model (gitea #110)" href="#2026-08-22--singleton-cache-ingest-model-gitea-110"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: kill the ~0.9 GiB/<code>/ingest</code> leak. <code>HTTP.Ingest</code> called <code>LoadModel()</code> per
request and <code>defer model.Close()</code>; <code>StaticModel.Close()</code> frees only the
tokenizer, not the safetensors matrix (measured 3 ingests: RSS 3.5→6.1 GiB;
~900-email backfill = OOM).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/brain/ingestmodel.go</code>: <code>embedder</code> interface (no Close — structural guarantee), <code>ingestModelCache</code> (mutex, failed loads not cached → retry next request), <code>getIngestModel()</code> process-wide singleton, <code>embedLeafs()</code> extracted from Ingest</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/brain/http.go</code> Ingest: <code>LoadModel()+defer Close()</code> → <code>getIngestModel()</code> + <code>embedLeafs</code>; shared model never closed per request</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/brain/ingestmodel_test.go</code> — single load across consecutive requests, 16-goroutine concurrent load-once, zero Close of shared model, retry-after-loader-error, pre-embedded skip</td>
<td>done — <code>zig go test -race -tags system_ladybug ./internal/brain/</code> green, <code>go vet</code> clean both tag modes, CGO search build green</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Notes: other <code>LoadModel()</code> callers (<code>bin/brain/add.go</code>, <code>index.go</code>,
<code>bin/facts/*</code>) are one-shot CLI processes — unchanged. Diff kept strictly to
model caching; connection lifecycle in the same function is #109 territory.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-23 — shared embedding model on the search path (gitea #335)</h2><a id="user-content-2026-09-23--shared-embedding-model-on-the-search-path-gitea-335" class="anchor" aria-label="Permalink: 2026-09-23 — shared embedding model on the search path (gitea #335)" href="#2026-09-23--shared-embedding-model-on-the-search-path-gitea-335"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: same fix as #110, extended to <code>/search</code>. <code>internal/brain/search.go</code>
<code>embedQuery</code> still called <code>loadModel()</code> + <code>defer model.Close()</code> on every
request, and <code>StaticModel.Close()</code> freed only the tokenizer — so each search
retained ~490 MiB inuse (~980 MiB allocated) until GC, and a live <code>brain-serve</code>
grew to 13 GiB (RSS 1.8 + swap 11.2) over the DB's 11 MB.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/brain/embedmodel.go</code> (renamed from <code>ingestmodel.go</code>): <code>embedder</code> (no Close), <code>embedModelCache</code>, <code>getEmbedModel()</code> process-wide singleton; now backs /ingest, the /search in-process fallback AND ANN extract</td>
<td>done</td>
</tr>
<tr>
<td><code>search.go</code> <code>embedQuery</code> → new <code>embedInProcess()</code> (cache, never Close); <code>serve()</code> (daemon) uses the shared model without closing; <code>anntool.go</code> <code>annExtractEmbed</code> drops its private <code>sync.Once</code> for the shared cache</td>
<td>done</td>
</tr>
<tr>
<td><code>model.go</code> <code>StaticModel.Close()</code> now releases <code>mat</code> too (one-shot CLI callers); shared instance is never closed</td>
<td>done</td>
</tr>
<tr>
<td><code>pkg/httpapi</code> pprof binds <code>127.0.0.1</code> instead of the API host (was <code>0.0.0.0:1707</code> via KB_HOST)</td>
<td>done</td>
</tr>
<tr>
<td><code>compose.yaml</code>: <code>KB_BUFFER_POOL</code> 4 GiB → 512 MiB, <code>deploy.resources.limits.memory: 2g</code> (container had <code>HostConfig.Memory=0</code>)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/brain/embedmodel_test.go</code> — 200 sequential search embeds load once and never Close; real <code>embedQuery</code> fallback loads once; <code>Close</code> frees the matrix; plus the #110 cache tests</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Live before/after (same host, <code>KBSEARCH_NO_DAEMON=1</code>, sequential
<code>test/stress --mix search:1 --c 1</code>): before, 13 searches grew RSS 147 MiB →
6.8 GiB (swap 0.5 GiB) with <code>alloc_space</code> <code>loadMatrix</code> 13.84 GiB; after, 49,448
searches kept <code>alloc_space</code> <code>loadMatrix</code> at 977 MB (one load), p50 6 ms vs
1429 ms, RSS post-warmup delta ~36 MiB (&lt; 50 MiB), swap flat. See <code>PERF.md</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)</h2><a id="user-content-2026-08-23--concurrency-audit-mailsync-corpuswatch-index-bench-gitea-94" class="anchor" aria-label="Permalink: 2026-08-23 — concurrency audit: mailsync, corpuswatch, index, bench (gitea #94)" href="#2026-08-23--concurrency-audit-mailsync-corpuswatch-index-bench-gitea-94"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Audit of the concurrency paths outside <code>internal/brain</code> db/conn lifecycle
(#109/#110 <code>brainMu</code>), driving the <code>internal/mailsync</code> worker pools, the
<code>corpuswatch</code> poll loop, the <code>bin/brain/index.go</code> bulk rebuild and the
<code>bin/reasoner/bench.go</code> bake-off. Goal: data races, unbounded concurrency,
unsynchronized shared state, goroutine leaks — hardening only, no semantic
change. Result per component (all verified with <code>go test -race</code>):</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Component</th>
<th>Verdict</th>
<th>Hazard(s) found</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/mailsync</code> <code>Run</code> worker pool + gmail/m365/onlyoffice sources</td>
<td>clean</td>
<td>none — counters already atomic, failures slice already under mutex, m365 token already channel-mutexed, bounded workers, no leak</td>
<td>none (test seam + race test only)</td>
</tr>
<tr>
<td><code>internal/corpuswatch</code> poll loop</td>
<td>clean</td>
<td>none — single-goroutine loop, <code>Stamp</code> is pure filesystem walk, no shared state</td>
<td>none (race test for <code>Stamp</code> concurrency)</td>
</tr>
<tr>
<td><code>bin/brain/index.go</code> → <code>WriteCorpus</code> → <code>parallelEmbed</code> + <code>ProgressReporter</code></td>
<td><strong>hazard fixed</strong></td>
<td><code>ProgressReporter.total</code> was a plain <code>int</code> written from embedding worker goroutines (<code>Report</code>) and read under the mutex in <code>render</code> → data race</td>
<td><code>total</code> → <code>atomic.Int64</code> (<code>internal/brain/corpus_pool.go</code>)</td>
</tr>
<tr>
<td><code>bin/reasoner/bench.go</code> → <code>internal/reasoner</code> <code>Client.Run</code></td>
<td>clean</td>
<td>none — client holds only <code>*http.Client</code> + immutable <code>Config</code>, <code>Run</code> keeps accounting in call-local state</td>
<td>none (race test for concurrent bench runs)</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/brain/corpus_pool.go</code>: <code>ProgressReporter.total int</code> → <code>atomic.Int64</code> (race on concurrent <code>Report</code>)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/brain/progress_race_test.go</code> — 16-goroutine concurrent <code>Report</code> + <code>Finish</code> under <code>-race</code>; total converges</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/mailsync/sync_race_test.go</code> — 8-worker <code>Run</code> over a fake <code>Source</code> under <code>-race</code> (test-only <code>sources</code> seam, no prod behavior change)</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/corpuswatch/stamp_race_test.go</code> — 16-goroutine concurrent <code>Stamp</code> under <code>-race</code></td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/reasoner/client_race_test.go</code> — 12 concurrent <code>Client.Run</code> + 16×10 shared-client <code>RunPrompt</code> under <code>-race</code> vs httptest</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test -race -count=1 ./internal/...</code> green; <code>go vet ./internal/...</code>
clean; touched files gofmt-clean; cgo Zig build of <code>bin/brain/search.go</code>
(<code>-tags system_ladybug</code>) green. Branch <code>audit/concurrency#94</code> off <code>release/v1</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)</h2><a id="user-content-2026-08-23--url-адресация-контента-селекторы-гранулярный-сплит-gitea-100" class="anchor" aria-label="Permalink: 2026-08-23 — URL-адресация контента, селекторы, гранулярный сплит (gitea #100)" href="#2026-08-23--url-адресация-контента-селекторы-гранулярный-сплит-gitea-100"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: stable content addressing for the ETL graph. Every extracted node gets a
canonical URL <code>scheme://platform/thread/msg/path-segments[#anchor]</code>, node ID =
<code>sha256(url)[:16]</code>, separate content ID = <code>sha256(body)</code>; parts reference each
other only via URLs. Content splits granularly BEFORE insertion into typed
Items (paragraph/heading/table/row/cell/image/link/page); a DOM/jQuery-like
selector mini-language addresses any leaf. Scope: addressing + selector +
split helpers with proof-of-use tests. Graph wiring (#99/#101, Ladybug) and
format coverage beyond html/md are out of scope here.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/address</code>: <code>Segment</code> (parse/render <code>type[index]</code>), <code>New</code>/<code>Parse</code> canonical URL (validated scheme/platform/thread/msg/segments/anchor), <code>NodeID(url)=sha256(url)[:16]</code> (32 hex), <code>ContentID(body)=sha256</code> (64 hex)</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/selector</code>: mini-language <code>p[3] &gt; table[0] &gt; tr[1] td[2]</code> (<code>&gt;</code> direct child, space descendant, optional index = any) — <code>Parse</code> (strict grammar errors) + <code>Apply(root)</code> over a typed Item tree</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/items</code>: <code>Kind</code> named int enum (page→<code>body</code>/heading/p/table/tr/td/img/a), typed <code>Item{Kind,Seg,Path,URL,Body,Src,Alt,Href,Children}</code>; <code>SplitHTML</code> (via <code>golang.org/x/net/html</code>) and <code>SplitMarkdown</code> (pipe-tables + standalone link/image) assign per-type indices and canonical URLs; content split happens BEFORE insertion</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>internal/address</code> round-trips + validation + id derivation; <code>internal/selector</code> parse/apply + edge cases (bad grammar, missing index, no-match); <code>internal/items</code> html/md fixtures (testdata/); <code>internal/items/e2e_test.go</code> — split→select→verify URL+node/content id, parsed back to same leaf</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test -race -count=1 ./internal/...</code> green; <code>go vet ./internal/...</code>
clean; new packages gofmt-clean. Branch <code>feat/content-url-addressing#100</code> off
<code>release/v1</code>. No end-to-end graph write (that's #99/#101 + Ladybug).</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — secret-scan в CI всех репо (gitea #142)</h2><a id="user-content-2026-08-23--secret-scan-в-ci-всех-репо-gitea-142" class="anchor" aria-label="Permalink: 2026-08-23 — secret-scan в CI всех репо (gitea #142)" href="#2026-08-23--secret-scan-в-ci-всех-репо-gitea-142"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: не допустить новых утечек секретов на этапе PR/push (после #140 — 8 HIGH-файлов в matrix).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Выбор инструмента: <strong>gitleaks</strong> (официальный образ <code>zricethezav/gitleaks</code>) — diff/commit-скан, fail-on-leak, generic+private-key правила</td>
<td>done</td>
</tr>
<tr>
<td>CI job <code>secret-scan</code> (pull_request + push, сканирует только diff новых коммитов) в 2dph (ci.yml), go-onlyoffice (test.yml), go-config (secret-scan.yml), matrix (.gitea/workflows/secret-scan.yml)</td>
<td>done — PR 2dph#143, go-onlyoffice#5, go-config#1, matrix#6</td>
</tr>
<tr>
<td>git pre-push + pre-commit хуки <code>scripts/githooks/</code> (+ install.sh), блокируют push/commit при находке</td>
<td>done</td>
</tr>
<tr>
<td>Скилы git-workflow + devops: правило «скан перед любым push»; синхронизированы в ai-fabric (release/v2)</td>
<td>done</td>
</tr>
<tr>
<td>Верификация: скан ловит реальные утечки matrix #140 (14 findings, exit 1); pre-push блокирует push с секретом</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)</h2><a id="user-content-2026-08-23--conversation-канон-модель--диск--манифест--граф-gitea-99-epic-88" class="anchor" aria-label="Permalink: 2026-08-23 — Conversation-канон: модель + диск + манифест + граф (gitea #99, epic #88)" href="#2026-08-23--conversation-канон-модель--диск--манифест--граф-gitea-99-epic-88"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: единая каноническая модель для почты и чатов — <code>Message{ID,ThreadID,Platform, From,ReplyTo,To,CC,BCC,SentAt,Body,Attachments(lazy)}</code>; хранение на диске под
<code>var/corpus/{mail,chats}</code> как JSON + манифест sha256 (идемпотентный upsert); граф
<code>(:Person)-[:SENT]-&gt;(:Message)-[:TO|CC|BCC]-&gt;(:Person)</code>, <code>[:REPLY_TO]</code>, <code>[:PART_OF]</code>
параграфы. Первый mergeable инкремент: ядро схемы + диск + манифест + round-trip
и golden-тест (один <code>.eml</code> и один telegram-экспорт дают одинаковую схему графа).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/canon</code> (evidence-слой, пакет-домен): <code>Person</code>/<code>Attachment</code>(lazy <code>Open(ctx)</code>, без буферизации тела)/<code>Message</code>/<code>Edge</code>/<code>EdgeType</code> (SENT/TO/CC/BCC/REPLY_TO/PART_OF), <code>Edges()</code>, <code>GraphSchema()</code></td>
<td>done</td>
</tr>
<tr>
<td><code>Store{root}</code>: <code>Write</code> (идемпотентный upsert по sha256 тела), <code>Read</code> (скипает битые файлы), <code>LoadManifest</code>/<code>SaveManifest</code> (атомарный); layout <code>root/&lt;platform&gt;/&lt;thread&gt;/&lt;id&gt;.json</code>; сегменты путей через <code>pkg/utils.SafeSegment</code></td>
<td>done</td>
</tr>
<tr>
<td><code>FromMail(r)</code> на emersion/go-message: envelope (From/To/Cc/Bcc через mail.Header.AddressList), ThreadID из References/Message-Id, ReplyTo из In-Reply-To, тело text/plain (fallback text/html → mailconv.StripHTML), вложения lazy (метаданные)</td>
<td>done</td>
</tr>
<tr>
<td><code>FromChat(platform,thread,text,from,ts,replyTo)</code>: Person id = <code>platform:handle</code>, парсинг ts (RFC3339), ReplyTo-цепочка</td>
<td>done</td>
</tr>
<tr>
<td>TDD (offline, фикстуры synthetic Alice/Bob/example.com): round-trip write→read→canon для mail и chat; идемпотентный повторный write; манифест = sha256(JSON); layout; Edges/схема; golden — mail и telegram дают одинаковую <code>GraphSchema()</code></td>
<td>done — 13 тестов, <code>-race</code> зелёные</td>
</tr>
<tr>
<td>docs/PLAN.md: секция #99 (этот блок)</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Осталось (вне первого инкремента): конверторы существующих источников (gmail/onlyoffice
<code>mailconv.Message</code>, <code>mailsync.Message</code>, whatsapp/linkedin экспорт) → канон; запись графа
в Ladybug (<code>(:Person)/:Message/:Paragraph</code> узлы + <code>SENT|TO|CC|BCC|REPLY_TO|PART_OF</code>
рёбра) — будет отдельным инкрементом на #99; миграция <code>bin/*/import</code> на канон.</p>
<p dir="auto">Verification: <code>go build ./...</code> green; <code>go vet ./internal/canon/... ./pkg/utils/...</code>
clean; <code>go test -race ./internal/canon/... ./pkg/utils/...</code> green. Branch
<code>feat/conversation-canon#99</code> off <code>release/v1</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)</h2><a id="user-content-2026-08-25--oo-mail-html-шаблон--render-check-до-отправки-gitea-76" class="anchor" aria-label="Permalink: 2026-08-25 — OO Mail: HTML-шаблон + render-check до отправки (gitea #76)" href="#2026-08-25--oo-mail-html-шаблон--render-check-до-отправки-gitea-76"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: из wave-1 письма уходили plain-text одной строкой; теперь брендированный
HTML-шаблон + гейт рендер-проверки, блокирующий send, пока черновик не пройдёт
assert (абзацы, подпись, logo cid, chat-line без дублей).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/oohtml</code> (новый пакет-домен): брендированный шаблон produktor.io (палитра #76: cream <code>#FAF5EA</code>, ink <code>#0A0A0A</code>, navy <code>#142039</code>/<code>#0E1626</code>, CTA <code>#F2C849</code>, primary <code>#143A6F</code>; system-ui; inline-стили; логотип <code>cid:produktor-logo.gif</code> без remote-URL)</td>
<td>done</td>
</tr>
<tr>
<td><code>RenderCheck(html, TemplateData)</code> — типизированный ассерт: paragraphs ≥2, greeting, offer+CTA, signature+site, logo cid, отсутствие дублей текстовых блоков (<code>Code</code> enum)</td>
<td>done</td>
</tr>
<tr>
<td><code>Mailer.Send(ctx, SendParams)</code> — TDD-гейт: <code>Build</code> → <code>SaveMailDraft</code>(HTML) → <code>GetMailMessage</code> back → <code>RenderCheck</code> → <code>SendMail</code> только при чистой проверке; иначе error «render check failed, send blocked»</td>
<td>done</td>
</tr>
<tr>
<td><code>BuildMessage(w, …)</code> — MIME <code>multipart/related</code> через <code>emersion/go-message</code>: HTML-часть + inline <code>image/gif</code> с <code>Content-ID</code> = логотип (cid-embedding, не remote fetch)</td>
<td>done</td>
</tr>
<tr>
<td>Лого-ассеты <code>etc/onlyoffice/assets/produktor-logo.{png,gif,-64.gif}</code></td>
<td>готовы</td>
</tr>
<tr>
<td>TDD offline (фикстуры synthetic Alice/Bob/example.com; fakeStore вместо сети): 10 тестов, <code>-race</code> зелёные</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go vet ./...</code> clean; <code>go test -race ./internal/oohtml/</code> green (10/10).
Branch <code>feat/ooo-mail-html-template#76</code> off <code>release/v1</code>. Осталось вне объёма:
wiring в OnlyOffice CLI (<code>oo mails send</code>) и фактическая отправка с вложенным
cid-логотипом через драфт-API OnlyOffice.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — CRM association two-source rule (gitea #55, epic #52)</h2><a id="user-content-2026-08-25--crm-association-two-source-rule-gitea-55-epic-52" class="anchor" aria-label="Permalink: 2026-08-25 — CRM association two-source rule (gitea #55, epic #52)" href="#2026-08-25--crm-association-two-source-rule-gitea-55-epic-52"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: <code>bin/facts/prove-crm</code> (issue references it as <code>bin/facts/crm</code>) должен
доказывать person→company И company→project как <code>root=facts</code> только когда оба
источника (corpus SoT <code>eslider/cv/projects/knowledge-mesh-seed.yaml</code> × ooCRM
<code>graph.json</code>) согласны; односторонние — mismatch, никогда fact.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>merge-правило вынесено из <code>main()</code> в <code>internal/facts.CRMAssocFacts</code> (single implementation, rule #10): person→company (companies_with_persons) + company→project (projects_contacts)</td>
<td>done</td>
</tr>
<tr>
<td>фикс ложного совпадения: generic-токены (<code>client</code>, <code>gmbh</code>, <code>ag</code>, <code>systems</code>, <code>platform</code>, …) больше не двигают company-match (<code>significantTokens</code> stopwords) — «Markets Platform (client)» больше не матчит «Client not named yet»</td>
<td>done</td>
</tr>
<tr>
<td>дедуп mismatches (раньше employer-kind loop дублировал «not found») + отчёт CRM-only компаний и проектов без company</td>
<td>done</td>
</tr>
<tr>
<td>TDD offline на synthetic-фикстурах: two-source rule + все mismatch-пути + generic-token регрессия, <code>-race</code> зелёные</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test -race ./internal/facts/</code> green (тесты crm/company-merge).
Против реального SoT (dry-run): proven=0; 9 corpus-org не в CRM; 4 CRM-only
компании (Capitole, Client not named yet, Teksystems, talabat); 198 проектов
без company — <code>projects_contacts[].companies</code> в <code>graph.json</code> пуст у всех
(данные заполняет внешний <code>graph.py</code>, вне репо). company→project пока недоказуем
из-за этого data-gap; person→company недоказуем — CRM-компании с персонами не
совпадают с corpus orgs. Branch <code>feat/crm-assoc#55</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)</h2><a id="user-content-2026-08-25--facts-lookup--contradiction-as-not-confirmed-test-plan-gitea-58-epic-52" class="anchor" aria-label="Permalink: 2026-08-25 — facts lookup + contradiction-as-not-confirmed test plan (gitea #58, epic #52)" href="#2026-08-25--facts-lookup--contradiction-as-not-confirmed-test-plan-gitea-58-epic-52"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: prove the <code>search → get → audit</code> operator flow on ONE known deal and the
contradiction-as-<code>(not confirmed)</code> path (D16), offline with synthetic fixtures.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>offline test: <code>search</code> (fuse FTS+vector, root filter) → <code>get</code> (leaf by id → source/confidence) → <code>audit</code> (<code>facts.CheckFactRow</code> + <code>facts.Adjudicate</code>) confirms two-source facts on synthetic deal <code>acme-2026</code> (Alice/Bob/example.com)</td>
<td>done</td>
</tr>
<tr>
<td>offline test: 2v2 contradiction leaf (<code>a x b vs c x d</code>, no rule fires) stays <code>(not confirmed)</code>/hypothesis until audited; never a confirmed fact; deduction escalates</td>
<td>done</td>
</tr>
<tr>
<td>operator flow documented for bot/assistant (<code>search</code>/<code>get</code>/<code>audit</code>; confirmed vs <code>(not confirmed)</code>) — <code>docs/facts/audit-recipes.md</code></td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go vet ./internal/facts/... ./internal/brain/rank/...</code> clean;
<code>go test -race ./internal/brain/rank/ ./internal/facts/</code> green
(<code>TestDealSearchGetAuditConfirmedFlow</code>, <code>TestDealContradictionNotConfirmedUntilAudited</code>).
Блокер #53 закрыт; live <code>get</code> против <code>var/kb.lbug</code> + OnlyOffice CRM остаётся
вне объёма (offline-план доказывает cgo-free логику). Branch <code>test/facts-lookup#58</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)</h2><a id="user-content-2026-08-25--facts-root-ingestion-repair-empty-facts-layer-gitea-181" class="anchor" aria-label="Permalink: 2026-08-25 — facts-root ingestion: repair empty facts layer (gitea #181)" href="#2026-08-25--facts-root-ingestion-repair-empty-facts-layer-gitea-181"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: the production brain had <strong>0 facts-root</strong> leafs — all 9204 in <code>info</code>
(9201 confirmed). Root cause: <code>bin/brain/index.go --rebuild</code> was run without
<code>--with-facts</code>; <code>internal/brain/corpus.go</code> writes every corpus leaf as
<code>root=info</code> (single file source), and the 2-source facts layer is a separate
<code>bin/facts/extract.go</code> stage. Audit: all 9201 confirmed info leafs are
single-source corpus leafs → <strong>0</strong> qualify for re-rooting by the ≥2-sources rule.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/brain</code> facts-promotion core: <code>SourceCount</code> (source string, <code>" x "</code> separator), <code>PromoteEligible</code> (≥2 sources + confirmed → facts), <code>PromoteFacts</code>/<code>EligibleInfoLeafs</code> (DB scan, idempotent upsert)</td>
<td>done</td>
</tr>
<tr>
<td>TDD tests: <code>TestSourceCount</code>, <code>TestPromoteEligible</code>, <code>TestPromoteFactsPromotesAndIdempotent</code>, <code>TestPromoteFactsReadPathDistinguishes</code> — <code>go test -race ./internal/brain/</code> green</td>
<td>done</td>
</tr>
<tr>
<td><code>bin/facts/promote.go</code> CLI (<code>--dry-run</code>, <code>--db</code>); <code>bin/facts/extract.go --dry-run</code> = 18 two-source facts</td>
<td>done</td>
</tr>
<tr>
<td>live DB: writes blocked (var/ owned 1001, no sudo; in-service <code>/ingest</code> bulk write hung the C layer → container restart). Full ingestion demonstrated on a faithful copy: before facts=0 info=9204 → after facts=18 info=9204</td>
<td>done</td>
</tr>
<tr>
<td>docs: <code>docs/facts/ingest.md</code> procedure + live runbook; <code>docs/brain/rebuild.md</code> facts note</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go vet ./internal/brain/ ./bin/facts/</code> clean;
<code>go test -race ./internal/brain/ -run 'TestSourceCount|TestPromoteEligible|TestPromoteFacts'</code> green.
Live write to <code>var/kb.lbug</code> requires the brain stopped + DB-owner (uid 1001/root)
write access — procedure in <code>docs/facts/ingest.md</code>. Branch <code>feat/facts-ingest#181</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)</h2><a id="user-content-2026-08-25--импорт-гэп-почтыконтактов-с-mnt8tb-gitea-79" class="anchor" aria-label="Permalink: 2026-08-25 — импорт-гэп почты/контактов с /mnt/8TB (gitea #79)" href="#2026-08-25--импорт-гэп-почтыконтактов-с-mnt8tb-gitea-79"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: закрыть гэп импорта дисковых источников почты/контактов на /mnt/8TB,
которые не были в пайплайне (#68/#74 импортировали только var/mail). Wave-1
(#84) уже импортировал mbox/.eml: tb-andriy-profile 35,368 · tb-backup-128g
39,435 · tb-2010-zip 1,289 · contacts-eml 125 (Krylov исключён). Этот шаг —
вынести mbox-сплиттер в тестируемый слой + доказать импорт тестами.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>инвентарь источников подтверждён на диске (read-only): TB-профиль Mail/ImapMail, оба PST (265 KB), Backup E-Mails, zip-2010, VCF/MAB — таблица-истина в <code>docs/mail-sources.md</code></td>
<td>done</td>
</tr>
<tr>
<td>mbox-сплиттер вынесен из build-tag скрипта в <code>internal/mailconv</code> (single implementation): <code>SplitMbox</code>, <code>SplitMboxDir</code>, <code>MboxMessage</code>, <code>LooksMbox</code>; <code>bin/mail/convert-mbox.go</code> — тонкая обёртка</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>TestSplitMbox</code>, <code>TestMboxMessage</code> (mbox→Message + source-тег), <code>TestSplitMboxDirIdempotent</code> (повторный прогон без дублей, контент-адресация sha256:16), <code>TestFromEMLFlatRoot</code> (contacts-eml плоский root) — <code>go test -race ./internal/mailconv/</code> зелёные</td>
<td>done</td>
</tr>
<tr>
<td>PST: <code>readpst</code> НЕ установлен → источник остаётся ЗАБЛОКИРОВАН; путь закрытия (libpst-utils → readpst -e → .eml → FromEML) задокументирован, не подделан</td>
<td>done</td>
</tr>
<tr>
<td>docs: <code>docs/mail-sources.md</code> (таблица-истина), runbook-секция, конфиг/abs-путей в коммитах нет</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go vet ./internal/mailconv/ ./bin/mail/</code> clean;
<code>go test -race ./internal/mailconv/ ./internal/source/ ./internal/mailsync/ ./internal/canon/ ./pkg/contact/</code> green. Исключения политики (#79): Krylov,
Drafts/Templates/Trash/Junk/Spam/Unsent, *.msf. Mail-send путь не трогали.
Branch <code>feat/mail-import-gap#79</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)</h2><a id="user-content-2026-08-25--docsskills-синк-под-текущий-тулзверь-агентов-gitea-81" class="anchor" aria-label="Permalink: 2026-08-25 — docs/skills: синк под текущий тулзверь агентов (gitea #81)" href="#2026-08-25--docsskills-синк-под-текущий-тулзверь-агентов-gitea-81"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: свежий агент по AGENTS.md делает sync-волну, reconcile report,
interaction write — без вопросов в чат. Git ops truth: git = HTTPS+token,
tea CLI для issues/PR (SSH deploy-key :222 на write сломан, #73 closed
obsoleted-by).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>AGENTS.md: git-ops truth (#81/#73), go-onlyoffice split «примитивы→lib, флоу→2dph», Tools-блок с sync-волной + reconcile + interaction, source map #79 + exclusion policy</td>
<td>done</td>
</tr>
<tr>
<td>skills: <code>api-client</code> — go-onlyoffice split; <code>etl-handler</code> — sync-волна + OO-флоу</td>
<td>done</td>
</tr>
<tr>
<td><code>docs/runbook.md</code> — секция «Sync wave + OO CRM» с verified-командами</td>
<td>done</td>
</tr>
<tr>
<td>verify: sync <code>--dry-run</code> печатает волну; reconcile/import-interaction <code>--help</code> и корпус-скан работают; OO-запись — только с env-кредами</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go run ./bin/stack/sync.go --dry-run</code> ok;
<code>go run -tags=onlyoffice_reconcile_contact ./bin/onlyoffice/reconcile-contact.go --help</code>
и <code>go run -tags=onlyoffice_import_interaction ./bin/onlyoffice/import-interaction.go --help</code>
ok (флаги <code>--sources/--write/--limit</code> совпадают с докой). Найдено при
верификации: <code>--only</code> в <code>bin/stack/sync.go</code> принимает имена шагов
(<code>mail,mail-import,chats,contact-brain,git-brain,contact-crm</code>), а не логические
группы — хелп-строка флага это врёт; документировано как есть, баг — на PO
(#81-коммент). Branch <code>docs/skills-sync#81</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)</h2><a id="user-content-2026-08-25--hnsw-leaf_vec-краш--in-process-cosine-scan-gitea-192" class="anchor" aria-label="Permalink: 2026-08-25 — HNSW Leaf_vec краш → in-process cosine scan (gitea #192)" href="#2026-08-25--hnsw-leaf_vec-краш--in-process-cosine-scan-gitea-192"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: liblbug 0.19.0 SIGSEGV на вставке в HNSW Leaf_vec (NULL deref в
simsimd_cos_f32, OnDiskHNSWIndex::shrinkForNode) при графе &gt;~1300 нод — наш
канон с ~28k leafs был под крашом (rc=139, тихий рестарт). Порт фикса mdx-1
(medex/brain-stack 6e0f379). Bug A (close/reopen race) у нас уже закрыт #109.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>rank/query.go</code>: VecStmt удалён (комментарий с rationale)</td>
<td>done</td>
</tr>
<tr>
<td><code>search.go</code>: queryVector → brute-force cosine scan по <code>l.embedding</code> (vecScanStmt), cosineToQuery + sqrt64; импорты math/sort</td>
<td>done</td>
</tr>
<tr>
<td><code>write.go</code>: EnsureIndexes создаёт только FTS <code>id</code>; Leaf_vec не создаётся/не требуется, существующий не дропается</td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>vector_test.go</code> — cosine known-vectors, zero-norm → 0 (не NaN), float64-элементы, truncation</td>
<td>done — <code>zig go test -tags system_ladybug ./internal/brain/</code> green (кроме пре-существующих modeldir env-failures), vet clean, gofmt clean</td>
</tr>
<tr>
<td>reindex live kb.lbug (<code>bin/brain/index.go --rebuild</code>) — ждёт PO-подтверждения</td>
<td>pending</td>
</tr>
<tr>
<td>PLAN.md блок</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>bin/cgo/zig go test -tags system_ladybug -skip TestModelDir ./internal/brain/</code> ok; <code>go test ./internal/brain/rank/</code> ok. Пре-существующие
падения <code>TestModelDir*</code> (HF-модель уже локально в models/, окружение) — не
связаны с этим изменением. Branch <code>fix/hnsw-cosine-scan#192</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-26 — весь mail-корпус в brain (gitea #199)</h2><a id="user-content-2026-08-26--весь-mail-корпус-в-brain-gitea-199" class="anchor" aria-label="Permalink: 2026-08-26 — весь mail-корпус в brain (gitea #199)" href="#2026-08-26--весь-mail-корпус-в-brain-gitea-199"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: оба корпуса — live <code>var/corpus/mail</code> и legacy <code>var/mail</code> (#184) — в
brain; шаг волны <code>mail-index</code>. Решение B: <code>LoadMailLeafs</code> расширен на
несколько root-ов, дедуп по sha256 content-address (одинаковое письмо в
обоих корпусах → один leaf), без копирования данных.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/brain/mail_corpus.go</code>: <code>MailRoots</code> + <code>LoadMailLeafs</code> multi-root + dedup; вынесен из <code>corpus.go</code> (cgo-free)</td>
<td>done</td>
</tr>
<tr>
<td><code>bin/brain/index.go --with-mail</code> → <code>brain.MailRoots(root)</code></td>
<td>done</td>
</tr>
<tr>
<td><code>bin/stack/sync.go</code>: шаг <code>mail-index</code> (после mail-import, <code>--with-mail</code>), brain runner с Zig CGO-тегами <code>brain_index</code></td>
<td>done</td>
</tr>
<tr>
<td>TDD: <code>mail_corpus_test.go</code> (multi-root, dedup, since, limit, attachment) + <code>sync_test.go</code> (mail-index план, runner-теги)</td>
<td>done — <code>go test -race ./internal/brain/... ./bin/stack/... ./internal/mailconv/...</code> green</td>
</tr>
<tr>
<td>Полный индекс <code>index.go --skip --with-mail</code> в существующую kb.lbug</td>
<td>done — 304 544 leafs, +7 712 новых (прошлый индекс был частичным после corrupt-199); итог info 313 786</td>
</tr>
<tr>
<td>Идемпотентность (повтор <code>--skip --with-mail</code>)</td>
<td>done — 0 новых</td>
</tr>
<tr>
<td>Search реальных писем (e2e-outbound, PST-тема)</td>
<td>done — count ≥ 1</td>
</tr>
<tr>
<td>docs: AGENTS.md / runbook <code>--only</code> список + <code>--with-mail</code></td>
<td>done</td>
</tr>
<tr>
<td>Известное ограничение: векторный поиск — линейный скан (#192), ~32s при 313k leafs</td>
<td>open — отдельный issue на оптимизацию</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Branch <code>feat/mail-corpus-brain#199</code>, PR #200 → release/v1.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)</h2><a id="user-content-2026-08-25--импорт-outlook-pst-readpst--e--eml--пайплайн-gitea-185" class="anchor" aria-label="Permalink: 2026-08-25 — импорт Outlook PST: readpst -e → .eml → пайплайн (gitea #185)" href="#2026-08-25--импорт-outlook-pst-readpst--e--eml--пайплайн-gitea-185"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: закрыть остаток #79 — два PST-файла (Andriy + копия в бэкапе 128Gb).
<code>readpst -e</code> → набор <code>.eml</code> → существующий <code>mailconv.FromEML</code> (source-тег <code>pst/</code>).
Конвертер по решению PO: readpst из пакета <code>pst-utils</code> (Ubuntu 24.04; бывший
<code>libpst-utils</code>).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Step</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>readpst на хост: системная установка <code>apt-get install pst-utils</code> заблокирована (нет passwordless sudo) — установлен локально в <code>var/dist/readpst</code> (dpkg-deb -x, <code>pst.readpst</code> в config.local.yml)</td>
<td>done (блокер зафиксирован в issue)</td>
</tr>
<tr>
<td><code>internal/source/pst.go</code>: адаптер <code>source.PST</code> (#97-паттерн) — <code>readpst -e</code> в вытираемый scratch (<code>var/tmp/pst/&lt;label&gt;</code>), политика #79 через <code>mailconv.SkipFolder</code> (немецкие имена: Entwürfe/Vorlagen/Gelöschte Objekte/Junk-E-Mail/Postausgang), контент-адресный копирование в корпус <code>Out/&lt;label&gt;/&lt;folder&gt;/&lt;sha256:16&gt;/&lt;sha256:16&gt;.eml</code>; <code>ImportPST</code>/<code>PlanPST</code> (sync + <code>mailconv.FromEML</code>), <code>--dry-run</code> план</td>
<td>done</td>
</tr>
<tr>
<td>нормализация <code>LibPST-iamunique-&lt;n&gt;</code> MIME-boundary (readpst вставляет случайный токен на каждом прогоне) — иначе контент-ID нестабильны и повторный прогон плодит дубли</td>
<td>done (найдено на реальных данных, TDD-тест)</td>
</tr>
<tr>
<td><code>bin/mail/import-pst.go</code>: тонкая CLI-обёртка (flaggy, конфиг pst.*, ctx-first, идемпотентность, state <code>var/state/pst.json</code>)</td>
<td>done</td>
</tr>
<tr>
<td>конфиг: <code>Config.PST</code> + секция <code>pst:</code> в <code>etc/brain/config.yml</code> (sources/readpst/out/state; отсутствие sources = явная ошибка с указанием на конфиг)</td>
<td>done</td>
</tr>
<tr>
<td>TDD offline (synthetic Alice/Bob/example.com, фейковый readpst через test-seam hook): конверсия .eml→canon (folder=Posteingang), идемпотентный повтор (0 дублей), state-чекпойнт, dry-run, policy-исключения, resolveReadPST (override→PATH→var/dist→ошибка), идентичные архивы (дедуп по seen-set)</td>
<td>done — <code>go test -race ./internal/source/ ./internal/mailconv/ ./internal/config/</code> green</td>
</tr>
<tr>
<td>Прогон реальных PST (оба файла байт-идентичны, sha256 совпадает): 1 уникальное письмо (Outlook-приветствие 2005, <code>Posteingang</code>) × 2 лейбла; повторный прогон new=0 skipped=2, дублей нет; readpst извлёк также 78 VCF + 6 ICS (импортируются другими пайплайнами, #68)</td>
<td>done</td>
</tr>
<tr>
<td>docs: <code>docs/mail-sources.md</code> (статусы PST → импортировано, секция PST-импорт), PLAN.md (этот блок)</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go build ./...</code> (кроме pre-existing build-tag пакетов bin/{reasoner,onlyoffice,postgres} — main без тега), <code>go vet ./...</code>, <code>go test -race ./...</code> зелёные; gofmt чист (shebang-файлы исключены). Корпус: <code>var/corpus/mail/pst/{pst-andriy,pst-backup-128g-vorlagen}</code> + state <code>var/state/pst.json</code> (1 seen-id). Branch <code>feat/pst-import#185</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)</h2><a id="user-content-2026-09-02--d-13-импорт-gator-wheregroup--граф-gitea-260-epic-257" class="anchor" aria-label="Permalink: 2026-09-02 — D-1.3: импорт gator wheregroup → граф (gitea #260, epic #257)" href="#2026-09-02--d-13-импорт-gator-wheregroup--граф-gitea-260-epic-257"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: пилот импорта канала <strong>wheregroup</strong> (3984 письма, канон gator kind=mail)
в kb.lbug как Message/Person узлы + рёбра SENT/TO/CC/BCC/REPLY_TO по write-пути
D-1.2 (#259) и коннектору D-1.1 (#258). Доказать связку «gator канон → граф
2dph» до gmail (D-1.4 #261). Read path не тронут; сырьё var/mail не читается.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pkg/duckdb</code>: <code>Exec</code>/<code>QueryRows</code> — произвольный SELECT поверх read_parquet как JSON-совместимые rows (gator-паттерн; LIST(STRUCT)→[]any из map, DECIMAL→float64)</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/mailgraph</code> (cgo-free): типизированный Row (message_id/folder/from/to/cc/bcc/in_reply_to/subject/date/body/content_hash), <code>MapRow</code>/<code>MapRows</code> (email lowercase, date RFC3339/TIMESTAMP, NULL-списки), <code>ReadSQL</code> (latest на message_id по observed_at + tombstone deleted исключён + channel), <code>ResolveThreads</code> (thread_id = корень цепочки in_reply_to, dangling → внешний корень, цикл → fallback), <code>SortByDate</code> (родители раньше ответов), <code>ToInputs</code> → <code>brain.MessageInput</code>, <code>ComputeStats</code> (folder-раскладка/REPLY_TO/уникальные email)</td>
<td>done</td>
</tr>
<tr>
<td>конфиг: <code>Config.Gator.MailHive</code> (<code>gator.mailhive</code>, шаблон etc/brain/config.yml + config.local.yml, флаг <code>--hive</code> / env <code>GATOR_MAIL_HIVE</code>)</td>
<td>done</td>
</tr>
<tr>
<td><code>bin/mail/graph.go</code> (cgo+system_ladybug+mail_graph): <code>--channel</code> + dry-run по умолчанию / <code>--commit</code>, <code>--db</code>, <code>--skip-existing</code> (инкремент), live-holder guard (--force), батч-транзакции, отчёт узлы/рёбра после записи</td>
<td>done</td>
</tr>
<tr>
<td>TDD: юнит cgo-free (MapRow null/регистр/date-форматы, Input/gator_ref <code>kind=mail#v-&lt;hash8&gt;</code>, SortByDate, ResolveThreads root/dangling/цикл, Stats порядок-независим, ReadSQL latest/tombstone/экранирование) + pkg/duckdb roundtrip parquet</td>
<td>done — зелёные</td>
</tr>
<tr>
<td>cgo-интеграция (temp Ladybug + synthetic hive parquet): полный цикл → узлы/рёбра, повтор = 0 дублей, <code>--skip-existing</code> = 0 новых, dangling REPLY_TO не создаётся</td>
<td>done — <code>TestImportCycle</code> (gcc ladybug+duckdb; CI-шаг добавлен, zig duckdb ABI не тянет)</td>
</tr>
<tr>
<td>live-прогон wheregroup 3984 → <code>var/kb.lbug</code> (бэкап <code>kb.lbug.pre-d13-260.bak</code>, compose brain остановлен на запись)</td>
<td>done — Message 0→3984, Person 0→167, рёбра SENT 3984 / TO 4855 / CC 302 / BCC 2 / REPLY_TO 639 (после повтора; первый прогон 630 — 9 рёбер досоздались, родитель позже ответа по date)</td>
</tr>
<tr>
<td>повторный прогон идемпотентен; <code>--skip-existing</code> = 0 записанных</td>
<td>done</td>
</tr>
<tr>
<td>спот-чек: folder INBOX 3143 / Sent 479 / INBOX/Unmatched 362 (сверка с gator), Person <a href="mailto:andriy.oblivantsev@wheregroup.com">andriy.oblivantsev@wheregroup.com</a>, gator_ref <code>kind=mail#v-&lt;hash8&gt;</code>, REPLY_TO-цепи, thread_id заполнен</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go vet ./internal/...</code> + <code>go test -race ./internal/mailgraph/ ./internal/config/ ./pkg/duckdb/ ./internal/canon/</code> green; cgo <code>go test -tags system_ladybug ./internal/mailgraph/</code> green (gcc); CLI dry-run/commit/повтор на live. Branch <code>feat/graph-import-wheregroup#260</code> off <code>main</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)</h2><a id="user-content-2026-09-03--n-11-фильтр-людикомпании-vs-сервис-аккаунты-для-accept-сетей-gitea-268-epic-267" class="anchor" aria-label="Permalink: 2026-09-03 — N-1.1: фильтр «люди/компании vs сервис-аккаунты» для accept-сетей (gitea #268, epic #267)" href="#2026-09-03--n-11-фильтр-людикомпании-vs-сервис-аккаунты-для-accept-сетей-gitea-268-epic-267"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: детерминированный классификатор адреса сети (L-9.5 #234) —
<code>person | company | service</code> — чтобы CRM-экспорт (<code>--accept-only</code>) не тащил
сервис-аккаунты/подсистемы/рассылки (GitLab/PayPal/LinkedIn/
markets-platform/chiliproject@trac), а реальные люди и компании-контекст
проходили. База — sender-эвристики <code>mailconv.IsMachineSender</code>
(junkDomains/junkLocalParts). Правила: docs/brain/crm-network-filter.md.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/mailconv/classify.go</code> (cgo-free): <code>ClassifySender(ParsedAddress)</code> → kind; каскад: релеи людей (linkedin hit-reply/inmail-hit-reply) → person; junk-домены mailconv (общий <code>isJunkDomain</code>, одна реализация) + сервис-домены сети (gitlab/paypal/markets-platform/xing/djinni/wellfound/…); local-автоматы (noreply/mailer-daemon/robot/рассылки/security); tool-имена подсистем (chiliproject/projeqtor/trac/wiki/…) в local и левом лейбле домена; <code>*-request</code>/<code>*-owner</code>/<code>lists.*</code> списки; имя-эвристика (Title/ALLCAPS/инициалы/частицы, org/role-слова); fallback first.last@домен; иначе company</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/network</code>: поле <code>Link.Kind</code> в BuildLinks (аддитивно, #234 API не ломает)</td>
<td>done</td>
</tr>
<tr>
<td><code>bin/network</code>: <code>--exclude-services</code> + <code>--accept-only</code> исключает kind=service; kind в text/YAML/JSON</td>
<td>done</td>
</tr>
<tr>
<td>TDD: юнит классификатора (домены/паттерны/границы noreply@люди, robots.txt в имени, релеи) + сети (Kind проставляется, сервисы не в accept-экспорте)</td>
<td>done — зелёные</td>
</tr>
<tr>
<td>Live-пилот на kb.lbug (read-only): гдеgroup top-60 accept = 44 person / 5 company / 11 service; eslider@ 1240 accept = 317 person / 494 company / 429 service; топ-сервисы eslider@ и chiliproject@trac — service, Astrid Emde/Ruby Rodriguez/Celine Schlindwein — person</td>
<td>done (таблица на #268)</td>
</tr>
<tr>
<td>Документация: docs/brain/crm-network-filter.md (правила/границы OPEN) + graph-network.md + PLAN.md</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test ./internal/mailconv/ ./internal/network/</code> + <code>go vet ./...</code>
green; cgo <code>bin/cgo/zig go vet -tags system_ladybug,network_cli ./bin/network/</code>
green. Branch <code>feat/network-human-filter#268</code> off <code>main</code>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)</h2><a id="user-content-2026-09-03--n-12-коннектор-oo-импорт-accept-манифеста-сети-в-crm-gitea-269-epic-267" class="anchor" aria-label="Permalink: 2026-09-03 — N-1.2: коннектор OO: импорт accept-манифеста сети в CRM (gitea #269, epic #267)" href="#2026-09-03--n-12-коннектор-oo-импорт-accept-манифеста-сети-в-crm-gitea-269-epic-267"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: write-инструмент — манифест accept-связей (bin/network --accept-only,
network.Manifest) → контакт Person в OnlyOffice CRM (email/имя/тег источника
<code>2dph:network:&lt;target&gt;</code>/about со сводкой + premises), идемпотентно по email;
сервис-аккаунты (N-1.1 #268) пропущены; dry-run/report-only ничего не пишет.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/network/manifest.go</code>: тип <code>network.Manifest</code> (crmDoc/crmLink из bin/network, L-9.5 #234) — общий контракт продюсера и коннектора; bin/network печатает его</td>
<td>done</td>
</tr>
<tr>
<td><code>internal/ooimport</code> (cgo-free): <code>ParseManifest</code> → <code>BuildPlan</code> (маппинг person/company→Person: имя через mailconv.SplitPersonName, fallback локальная часть email; kind=service/без email → skipped; тег default <code>2dph:network:&lt;target-email&gt;</code>; about: <code>{msgs} писем / {threads} тредов / {replies} ответов, период {period}; premises: mail/commit … (+N ещё); source: &lt;manifest&gt;</code>) → <code>Run</code> (BuildContactEmailIndex один проход → matched skip / create: CreatePerson + AddContactInfo email primary + CreateContactTag/AddContactTag; rollback созданного при сбое email/тега; dry-run/limit)</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>bin/onlyoffice/import-network.go</code> (shebang, tag onlyoffice_import_network): `--manifest &lt;file</td>
<td>-&gt;<code>(обязателен),</code>--dry-run<code>/</code>--write<code>(mutually exclusive),</code>--limit N<code>, </code>--tag`; report only по умолчанию; отчёт created/matched/skipped/failed/pending</td>
</tr>
<tr>
<td>Маппинг-решения: linkedin-релеи людей (<a href="mailto:hit-reply@linkedin.com">hit-reply@linkedin.com</a>, display name реальный) — Person как есть (email=релей, имя=человек); роль-ящики/компании (info@/alle@, kind=company) — Person с fallback-именем (компания-группировка — N-1.4 #271); kind=service не идёт</td>
<td>done (док: docs/brain/oo-network-import.md)</td>
</tr>
<tr>
<td>TDD офлайн: ParseManifest (+roundtrip продюсер-консьюмер yaml), BuildPlan (имя/email lowercase/тег/about-точная строка/sort/skip), splitByExisting (повтор = 0 новых)</td>
<td>done — зелёные</td>
</tr>
<tr>
<td>Интеграция с живым OO (<code>//go:build integration</code>, creds ONLYOFFICE_URL/USER/PASS, без creds — skip): dry-run ничего не пишет → write создаёт контакт email/тег/about → повтор = 0 новых/all matched; --limit создаёт часть, добивание — остальное; тестовый контакт удаляется в t.Cleanup (тег остаётся 0-count — API не удаляет теги)</td>
<td>done — зелёные на office.produktor.io</td>
</tr>
<tr>
<td>Документация: docs/brain/oo-network-import.md (маппинг/команды/границы) + graph-network.md §4 + PLAN.md</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test ./internal/network/ ./internal/mailconv/ ./internal/ooimport/</code></p>
<ul dir="auto">
<li><code>go vet ./...</code> green; integration <code>go test -tags=integration ./internal/ooimport/</code>
green (live OO); CLI dry-run на fixture — report-only, ничего не пишет.
Branch <code>feat/oo-crm-connector#269</code> off <code>main</code>.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)</h2><a id="user-content-2026-09-03--n-14-компания-группировка-по-домену--дописывание-тегаabout-matched-gitea-271-epic-267" class="anchor" aria-label="Permalink: 2026-09-03 — N-1.4: компания-группировка по домену + дописывание тега/about matched (gitea #271, epic #267)" href="#2026-09-03--n-14-компания-группировка-по-домену--дописывание-тегаabout-matched-gitea-271-epic-267"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: по итогам пилота N-1.3 (#270) довести гдеgroup-импорт до структуры
«деловой контакт»: (1) role-ящики/компании гдеgroup сгруппировать под
компанией по домену email; (2) существующим matched-контактам (32 из #270,
в т.ч. топ-5 из #85) <strong>аддитивно</strong> дописать тег <code>2dph:network:&lt;target&gt;</code> +
about-premises-сводку (решение владельца #267); (3) дедуп — только report
(реальных дублей пилот не выявил, мульти-адреса — YAGNI эпика).</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>internal/ooimport</code>: поле <code>Contact.Kind</code> (класс связи манифеста person/company; service отфильтрован) — основа для группировки</td>
<td>done</td>
</tr>
<tr>
<td><code>company.go</code>: <code>CompanyRule</code> (домен→компания), <code>ParseCompanies</code> (YAML, валидация: домен с точкой, не email, без повторов; сорт), <code>EmailDomain</code>, <code>GroupCompanyLinks</code> (только kind=company → компания по домену; person-kind не трогаем; company без правила — unmapped; два домена → одна компания схлопываются)</td>
<td>done</td>
</tr>
<tr>
<td><code>RunCompanies</code> (write/dry-run): FindCompany → если нет CreateCompany → GetContact + UpdatePerson с companyId; повтор = 0 изменений (found/already); роль-ящик не в CRM — notfound</td>
<td>done</td>
</tr>
<tr>
<td><code>backfill.go</code>: <code>BackfillChanges</code> (тег если нет; about-сводка только если about пуст/пробельный — ручные правки не перезаписываем), <code>RunBackfill</code> (hasTag по ListContactsByTag, about/имена GetContact; dry-run Would; CreateContactTag идемпотентно; повтор = 0 Updated)</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>import-network.go</code>: флаги <code>--backfill</code>, `--companies &lt;yaml</td>
<td>-&gt;` (после Run; оба идемпотентны; отчёты updated/would/unchanged + found/created/linked/already/unmapped)</td>
</tr>
<tr>
<td>Косметика: <code>mailconv.SplitPersonName</code> снимает трейлинговую « (Org)»-декорацию («Astrid Emde (FOSSGIS e.V.)» → Astrid/Emde; пилот #270: given «Astrid Emde (FOSSGIS»/family «e.V.)»)</td>
<td>done</td>
</tr>
<tr>
<td>TDD офлайн: mailconv SplitPersonName (декорация/без/вложенная/вся-в-скобках → fallback), ParseCompanies (сорт/трим/ошибки), GroupCompanyLinks (role-ящики/сорт/unmapped/person-kind не трогаем), BackfillChanges (аддитивность: ручной about цел, пробельный about — сводка)</td>
<td>done — зелёные</td>
</tr>
<tr>
<td>Интеграция с живым OO (<code>//go:build integration</code>, без creds — skip): role-ящик → компания создана+слинкован → повтор already=1; matched: тег+about дописаны, ручной about B не перезаписан, повтор = 0 Updated</td>
<td>done (office.produktor.io)</td>
</tr>
<tr>
<td>Документация: docs/brain/oo-network-import.md (N-1.4: --backfill/--companies, границы: person-kind не линкуем, Name маппинга = display name компании CRM) + PLAN.md</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Verification: <code>go test ./internal/mailconv/ ./internal/ooimport/ ./internal/network/</code></p>
<ul dir="auto">
<li><code>go vet ./...</code> green; integration <code>go test -tags=integration ./internal/ooimport/</code>
green (live OO); CLI <code>go vet -tags=onlyoffice_import_network ./bin/onlyoffice/</code>.
Дедуп: реальных дублей Person/компаний пилот не выявил — merge не
применялся (только report; правило #271 «строго по явному списку»).
Branch <code>feat/oo-crm-company#271</code> off <code>main</code>.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)</h2><a id="user-content-2026-09-03--p-95-сервисные-клиенты-поверх-read-контракта-gitea-241-epic-239" class="anchor" aria-label="Permalink: 2026-09-03 — P-9.5: сервисные клиенты поверх read-контракта (gitea #241, epic #239)" href="#2026-09-03--p-95-сервисные-клиенты-поверх-read-контракта-gitea-241-epic-239"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Goal: единый клиентский слой чтения brain — typed-клиент поверх read-контракта
(P-9.4 #240) вместо того, что каждый потребитель зовёт bin/brain/HTTP/MCP
по-своему; плюс «гейт facts» (клиент не выдаёт not-confirmed как подтверждённое).
Клиент читает только через контракт (HTTP :8630), kb.lbug напрямую не открывает.</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Item</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pkg/brainclient</code> (cgo-free): <code>Client</code> (Config base/token/timeout, транспорт) + typed <code>Search</code>/<code>Get</code>/<code>Stats</code>/<code>Audit</code> → <code>internal/contract</code>-ответы; ответы валидируются контрактными валидаторами (формат + contract_version; несовместимый сервис — ошибка)</td>
<td>done</td>
</tr>
<tr>
<td>Гейт facts: <code>Gate(root, confidence)</code> (confirmed только facts+confirmed/legacy-пустое), <code>Client.Facts</code> (search --root facts → confirmed[] / not_confirmed[] с reason), <code>GateAudit</code> (гистограмма facts-корня; not_confirmed&gt;0 = нарушение). 2v2-противоречия (hypothesis, D16) в confirmed не попадают</td>
<td>done</td>
</tr>
<tr>
<td>CLI <code>bin/brain/client.go</code> (shebang, brain_client, cgo-free): search/get/stats/audit, --json/--root facts|info/--as-of/-n/--no-web/--base/--token; реализация в <code>pkg/brainclient/cli</code> (Run, покрыта тестами); audit при not_confirmed&gt;0 → exit 1</td>
<td>done</td>
</tr>
<tr>
<td>TDD: gate-таблицы (facts+confirmed/legacy vs hypothesis/partial/info), GateSearch/GateAudit на фикстурах; httptest-клиент (typed-декод, валидация, ошибки HTTP, auth-header, root=facts параметр); CLI-проводка (флаги, JSON-формы, exit-коды)</td>
<td>done — <code>go test ./pkg/brainclient/...</code> зелёные</td>
</tr>
<tr>
<td>Live на :8630 (db 105220 leafs): stats/audit (facts=21 confirmed, not_confirmed=0, exit 0), search --root facts (onlyoffice postgres → 1 confirmed), get --body (source «docker ps x compose:...»), info-хиты в default-режиме помечены (not confirmed)</td>
<td>done</td>
</tr>
<tr>
<td>Документация: docs/brain/read-contract.md §«Клиентский слой (P-9.5)» (SDK/CLI, гейт, примеры search-факт + audit-карточка, кросс-репо путь через P-9.6) + skills/brain/SKILL.md §«Read через контракт» + PLAN.md</td>
<td>done</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">Кросс-репо (gator/cv): вынос read-контракта и клиента в публичный go-* модуль —
P-9.6 (ADR, эпик #239); до него внешние потребители читают по форматам
read-contract.md через HTTP/MCP/CLI.</p>
<p dir="auto">Verification: <code>go vet ./...</code> + <code>go test ./pkg/brainclient/...</code> green
(CLI-логика в pkg/brainclient/cli входит в обычный ./...; bin/brain — shebang-
mains за эксклюзивными тегами, по D14 компилируются пофайлово) —
bin/brain/client.go собран и прогнан <code>go run -tags=brain_client</code>;
<code>./bin/facts/audit.go self</code> ok. Branch <code>feat/brain-client#241</code> off <code>main</code>.</p>
</article></div><div class="d-none"></div></section></div></div></div> </div> <!-- --> </div></div></div></div></div></div><div class="ScrollMarksContainer-module__scrollMarksContainer__Eu7uU" id="find-result-marks-container"></div><div class="d-none"></div><div class="d-none"></div></div> <!-- --> <!-- --> </div>
</react-app>




  </div>

</turbo-frame>

    </main>
  </div>

  </div>

          <footer class="footer f6 color-fg-muted color-border-subtle tmp-pt-7 tmp-pb-6 p-responsive" role="contentinfo"  >
  <h2 class='sr-only'>Footer</h2>

  


  <div class="d-flex flex-justify-center flex-items-center flex-column-reverse flex-lg-row flex-wrap flex-lg-nowrap">
    <div class="d-flex flex-items-center flex-shrink-0 mx-2">
      <a aria-label="GitHub Homepage" class="footer-octicon mr-2" href="https://github.com">
        <svg aria-hidden="true" data-component="Octicon" height="24" viewBox="0 0 24 24" version="1.1" width="24" data-view-component="true" class="octicon octicon-mark-github">
    <path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path>
</svg>
</a>
      <span>
        &copy; 2026 GitHub,&nbsp;Inc.
      </span>
    </div>

    <nav aria-label="Footer">
      <h3 class="sr-only" id="sr-footer-heading">Footer navigation</h3>

      <ul class="list-style-none d-flex flex-justify-center flex-wrap mb-2 mb-lg-0" aria-labelledby="sr-footer-heading">


          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to Terms&quot;,&quot;label&quot;:&quot;text:terms&quot;}" href="https://docs.github.com/site-policy/github-terms/github-terms-of-service" data-view-component="true" class="Link--secondary Link">Terms</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to privacy&quot;,&quot;label&quot;:&quot;text:privacy&quot;}" href="https://docs.github.com/site-policy/privacy-policies/github-privacy-statement" data-view-component="true" class="Link--secondary Link">Privacy</a>
          </li>


            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to security&quot;,&quot;label&quot;:&quot;text:security&quot;}" href="https://github.com/security" data-view-component="true" class="Link--secondary Link">Security</a>
            </li>

            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to status&quot;,&quot;label&quot;:&quot;text:status&quot;}" href="https://www.githubstatus.com/" data-view-component="true" class="Link--secondary Link">Status</a>
            </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to community&quot;,&quot;label&quot;:&quot;text:community&quot;}" href="https://github.community/" data-view-component="true" class="Link--secondary Link">Community</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to docs&quot;,&quot;label&quot;:&quot;text:docs&quot;}" href="https://docs.github.com/" data-view-component="true" class="Link--secondary Link">Docs</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to contact&quot;,&quot;label&quot;:&quot;text:contact&quot;}" href="https://support.github.com?tags=dotcom-footer" data-view-component="true" class="Link--secondary Link">Contact</a>
          </li>

          
<li class="mx-2" >
  <cookie-consent-link>
    <button
      type="button"
      class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent"
      data-action="click:cookie-consent-link#showConsentManagement"
      data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;cookies&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;cookies_link_subfooter_footer&quot;}"
    >
      Manage cookies
    </button>
  </cookie-consent-link>
</li>

  <li class="mx-2">
    <cookie-consent-link>
      <button
        type="button"
        class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent text-left"
        data-action="click:cookie-consent-link#showConsentManagement"
        data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;dont_share_info&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;dont_share_info_link_subfooter_footer&quot;}"
      >
        Do not share my personal information
      </button>
    </cookie-consent-link>
  </li>

      </ul>
    </nav>
  </div>
</footer>



    <ghcc-consent id="ghcc" class="position-fixed bottom-0 left-0" style="z-index: 999999"
      data-locale="en"
      data-initial-cookie-consent-allowed=""
      data-cookie-consent-required="true"
    ></ghcc-consent>




  <div id="ajax-error-message" class="ajax-error-message flash flash-error" hidden>
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    You can’t perform that action at this time.
  </div>

    <template id="site-details-dialog">
  <details class="details-reset details-overlay details-overlay-dark lh-default color-fg-default hx_rsm" open>
    <summary role="button" aria-label="Close dialog"></summary>
    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
      <button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
      </button>
      <div class="octocat-spinner tmp-my-6 js-details-dialog-spinner"></div>
    </details-dialog>
  </details>
</template>

    <div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;">
  <div class="Popover-message Popover-message--bottom-left Popover-message--large Box color-shadow-large" style="width:360px;">
  </div>
</div>

    <template id="snippet-clipboard-copy-button">
  <div class="zeroclipboard-container position-absolute right-0 top-0">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon m-2 tmp-m-2">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none m-2 tmp-m-2">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>
<template id="snippet-clipboard-copy-button-unpositioned">
  <div class="zeroclipboard-container">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>




    </div>
    <div id="js-global-screen-reader-notice" class="sr-only mt-n1" aria-live="polite" aria-atomic="true" ></div>
    <div id="js-global-screen-reader-notice-assertive" class="sr-only mt-n1" aria-live="assertive" aria-atomic="true"></div>
  </body>
</html>

