ltcai 11.1.0 → 11.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -54
- package/docs/CHANGELOG.md +94 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
- package/docs/LAYOUT_REBUILD_SPEC.md +9 -1
- package/docs/MULTI_AGENT_RUNTIME.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -2
- package/docs/PERMISSION_MODE.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/architecture.md +6 -2
- package/docs/kg-schema.md +2 -2
- package/docs/v11.3.0_PLAN.md +202 -0
- package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +176 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/gates.py +125 -0
- package/lattice_brain/graph/_kg_common/__init__.py +287 -0
- package/lattice_brain/graph/_kg_common/extraction.py +516 -0
- package/lattice_brain/graph/_kg_common/relations.py +161 -0
- package/lattice_brain/graph/_kg_common/text.py +479 -0
- package/lattice_brain/graph/discovery_index/__init__.py +35 -0
- package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
- package/lattice_brain/graph/discovery_index/extract.py +137 -0
- package/lattice_brain/graph/discovery_index/scan.py +411 -0
- package/lattice_brain/graph/discovery_index/upsert.py +495 -0
- package/lattice_brain/graph/fusion.py +35 -4
- package/lattice_brain/graph/projection/__init__.py +42 -0
- package/lattice_brain/graph/projection/curation.py +500 -0
- package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +81 -485
- package/lattice_brain/graph/retrieval/__init__.py +54 -0
- package/lattice_brain/graph/retrieval/context.py +197 -0
- package/lattice_brain/graph/retrieval/graph_view.py +319 -0
- package/lattice_brain/graph/retrieval/hybrid.py +488 -0
- package/lattice_brain/graph/retrieval/maintenance.py +121 -0
- package/lattice_brain/graph/retrieval/signals.py +95 -0
- package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
- package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
- package/lattice_brain/graph/retrieval_vector/search.py +560 -0
- package/lattice_brain/graph/retrieval_vector/status.py +374 -0
- package/lattice_brain/graph/schema.py +9 -0
- package/lattice_brain/graph/store.py +9 -0
- package/lattice_brain/graph/vector_index/selector.py +32 -2
- package/lattice_brain/ingestion/__init__.py +130 -0
- package/lattice_brain/ingestion/_contract.py +90 -0
- package/lattice_brain/ingestion/constants.py +127 -0
- package/lattice_brain/ingestion/folder_scan.py +57 -0
- package/lattice_brain/ingestion/folders.py +258 -0
- package/lattice_brain/ingestion/hashing.py +26 -0
- package/lattice_brain/ingestion/jobs_api.py +107 -0
- package/lattice_brain/ingestion/models.py +80 -0
- package/lattice_brain/ingestion/pipeline.py +486 -0
- package/lattice_brain/ingestion/quality.py +209 -0
- package/lattice_brain/ingestion/routing.py +295 -0
- package/lattice_brain/multimodal/__init__.py +164 -0
- package/lattice_brain/multimodal/audio.py +77 -0
- package/lattice_brain/multimodal/common.py +118 -0
- package/lattice_brain/{multimodal.py → multimodal/images.py} +22 -262
- package/lattice_brain/multimodal/ports.py +169 -0
- package/lattice_brain/multimodal/video.py +410 -0
- package/lattice_brain/portability/__init__.py +90 -0
- package/lattice_brain/portability/_contract.py +42 -0
- package/lattice_brain/portability/backups.py +338 -0
- package/lattice_brain/portability/bundles.py +136 -0
- package/lattice_brain/portability/constants.py +93 -0
- package/lattice_brain/portability/fsops.py +138 -0
- package/lattice_brain/portability/service.py +41 -0
- package/lattice_brain/portability/sharing.py +714 -0
- package/lattice_brain/runtime/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/sealed_box.py +244 -0
- package/lattice_brain/synthesis.py +24 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/brain_intelligence.py +4 -0
- package/latticeai/api/chat.py +11 -0
- package/latticeai/api/chat_helpers.py +16 -3
- package/latticeai/api/chat_hybrid.py +32 -1
- package/latticeai/api/chronicle.py +63 -0
- package/latticeai/api/features.py +70 -0
- package/latticeai/api/local_files.py +102 -0
- package/latticeai/api/portability.py +39 -4
- package/latticeai/api/review_queue.py +126 -0
- package/latticeai/api/search.py +16 -2
- package/latticeai/core/agent/__init__.py +93 -0
- package/latticeai/core/agent/_contract.py +79 -0
- package/latticeai/core/agent/context.py +57 -0
- package/latticeai/core/agent/deps.py +125 -0
- package/latticeai/core/agent/execution.py +622 -0
- package/latticeai/core/agent/planning.py +145 -0
- package/latticeai/core/agent/recovery.py +157 -0
- package/latticeai/core/agent/runtime.py +210 -0
- package/latticeai/core/agent/verification.py +231 -0
- package/latticeai/core/config.py +4 -1
- package/latticeai/core/context_builder.py +6 -3
- package/latticeai/core/embedding_providers/__init__.py +151 -0
- package/latticeai/core/embedding_providers/base.py +199 -0
- package/latticeai/core/embedding_providers/captions.py +162 -0
- package/latticeai/core/embedding_providers/profiles.py +126 -0
- package/latticeai/core/embedding_providers/text.py +350 -0
- package/latticeai/core/embedding_providers/vision.py +352 -0
- package/latticeai/core/file_generation/__init__.py +115 -0
- package/latticeai/core/file_generation/bundles.py +76 -0
- package/latticeai/core/file_generation/extraction.py +154 -0
- package/latticeai/core/file_generation/inference.py +235 -0
- package/latticeai/core/file_generation/orchestration.py +152 -0
- package/latticeai/core/file_generation/prompting.py +117 -0
- package/latticeai/core/file_generation/repair.py +114 -0
- package/latticeai/core/file_generation/sanitize.py +61 -0
- package/latticeai/core/file_generation/validation.py +201 -0
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/messages.py +152 -0
- package/latticeai/core/model_compat.py +73 -2
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/integrations/telegram_bot/__init__.py +123 -0
- package/latticeai/integrations/telegram_bot/__main__.py +17 -0
- package/latticeai/integrations/telegram_bot/config.py +86 -0
- package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
- package/latticeai/integrations/telegram_bot/flows.py +478 -0
- package/latticeai/integrations/telegram_bot/helpers.py +322 -0
- package/latticeai/integrations/telegram_bot/screens.py +394 -0
- package/latticeai/models/model_providers.py +12 -4
- package/latticeai/models/router/__init__.py +88 -0
- package/latticeai/models/router/_contract.py +66 -0
- package/latticeai/models/router/branding.py +56 -0
- package/latticeai/models/router/catalog.py +69 -0
- package/latticeai/models/router/documents.py +199 -0
- package/latticeai/models/router/errors.py +37 -0
- package/latticeai/models/router/generation.py +258 -0
- package/latticeai/models/router/loading.py +291 -0
- package/latticeai/models/router/local_models.py +85 -0
- package/latticeai/models/router/registry.py +147 -0
- package/latticeai/runtime/build_phases/__init__.py +82 -0
- package/latticeai/runtime/build_phases/features.py +407 -0
- package/latticeai/runtime/build_phases/foundation.py +555 -0
- package/latticeai/runtime/build_phases/web.py +492 -0
- package/latticeai/runtime/chat_wiring.py +4 -0
- package/latticeai/runtime/feature_toggle_wiring.py +163 -0
- package/latticeai/runtime/router_registration.py +11 -0
- package/latticeai/runtime/runtime_context.py +1 -0
- package/latticeai/services/app_context.py +8 -0
- package/latticeai/services/architecture_readiness.py +48 -19
- package/latticeai/services/automation_intelligence.py +22 -2
- package/latticeai/services/brain_intelligence/__init__.py +58 -0
- package/latticeai/services/brain_intelligence/_contract.py +71 -0
- package/latticeai/services/brain_intelligence/consistency.py +193 -0
- package/latticeai/services/brain_intelligence/constants.py +47 -0
- package/latticeai/services/brain_intelligence/digest.py +258 -0
- package/latticeai/services/brain_intelligence/health.py +331 -0
- package/latticeai/services/brain_intelligence/proposals.py +264 -0
- package/latticeai/services/brain_intelligence/sampling.py +84 -0
- package/latticeai/services/brain_intelligence/service.py +48 -0
- package/latticeai/services/chronicle.py +557 -0
- package/latticeai/services/command_center.py +10 -4
- package/latticeai/services/feature_toggles.py +502 -0
- package/latticeai/services/folder_watch.py +122 -1
- package/latticeai/services/hybrid_chat.py +56 -5
- package/latticeai/services/interop_bridges.py +978 -0
- package/latticeai/services/memory_service/__init__.py +52 -0
- package/latticeai/services/memory_service/_contract.py +100 -0
- package/latticeai/services/memory_service/brief.py +431 -0
- package/latticeai/services/memory_service/constants.py +57 -0
- package/latticeai/services/memory_service/maintenance.py +138 -0
- package/latticeai/services/memory_service/manager.py +186 -0
- package/latticeai/services/memory_service/proof.py +136 -0
- package/latticeai/services/memory_service/recall.py +225 -0
- package/latticeai/services/memory_service/service.py +48 -0
- package/latticeai/services/memory_service/stores.py +110 -0
- package/latticeai/services/model_capability_registry.py +434 -261
- package/latticeai/services/model_catalog.py +95 -61
- package/latticeai/services/model_recommendation.py +18 -11
- package/latticeai/services/model_runtime/__init__.py +322 -0
- package/latticeai/services/model_runtime/cloud.py +87 -0
- package/latticeai/services/model_runtime/download.py +282 -0
- package/latticeai/services/model_runtime/engines.py +341 -0
- package/latticeai/services/model_runtime/loading.py +178 -0
- package/latticeai/services/model_runtime/service.py +129 -0
- package/latticeai/services/model_runtime/state.py +131 -0
- package/latticeai/services/model_runtime/status.py +255 -0
- package/latticeai/services/multimodal_ports.py +26 -1
- package/latticeai/services/obsidian_bridge.py +16 -25
- package/latticeai/services/product_readiness.py +15 -7
- package/latticeai/services/search_service.py +149 -2
- package/latticeai/services/tool_dispatch.py +4 -0
- package/latticeai/setup/auto_setup.py +27 -30
- package/latticeai/setup/wizard/__init__.py +126 -0
- package/latticeai/setup/wizard/catalog.py +172 -0
- package/latticeai/setup/wizard/detect.py +323 -0
- package/latticeai/setup/wizard/install.py +348 -0
- package/latticeai/setup/wizard/paths.py +168 -0
- package/latticeai/setup/wizard/plans.py +74 -0
- package/latticeai/setup/wizard/recommend.py +320 -0
- package/package.json +6 -2
- package/scripts/bump_version.py +14 -0
- package/scripts/capture_release_evidence.mjs +33 -21
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_i18n_namespace_coverage.mjs +41 -4
- package/scripts/check_max_file_lines.mjs +102 -0
- package/scripts/check_release_evidence_bound.mjs +30 -15
- package/scripts/check_screenshot_pixel_delta.py +34 -4
- package/scripts/check_server_i18n.mjs +2 -0
- package/scripts/generate_rust_parity_fixtures.py +562 -0
- package/scripts/lib/mock_server_fingerprint.mjs +94 -0
- package/scripts/release_screen_claims.json +44 -2
- package/scripts/verify_hf_model_registry.py +253 -218
- package/src-tauri/Cargo.lock +361 -3
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/src/backend.rs +349 -0
- package/src-tauri/src/folder.rs +33 -0
- package/src-tauri/src/main.rs +97 -399
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +41 -37
- package/static/app/assets/Act-yYpYnn0v.js +1 -0
- package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
- package/static/app/assets/{Brain-CzCsI1mi.js → Brain-C1HBN0Wf.js} +2 -2
- package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
- package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
- package/static/app/assets/Capture-CFIRsFNE.js +1 -0
- package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
- package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
- package/static/app/assets/Library-DwO3yZST.js +1 -0
- package/static/app/assets/{LivingBrain-BXMWIK_2.js → LivingBrain-Jn1GK0-S.js} +1 -1
- package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
- package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
- package/static/app/assets/System-DW8F-2xL.js +1 -0
- package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
- package/static/app/assets/{bot-4BvN07ux.js → bot-IM_E_Y12.js} +1 -1
- package/static/app/assets/brain-Ci1CkWjM.js +1 -0
- package/static/app/assets/{button-CDjtnAoU.js → button-COwyqfHM.js} +1 -1
- package/static/app/assets/circle-check-DfInj-qD.js +1 -0
- package/static/app/assets/{circle-pause-D_RMn7tp.js → circle-pause-DEM4A1Y5.js} +1 -1
- package/static/app/assets/{circle-play-B5OpB8ae.js → circle-play-C9djDuLd.js} +1 -1
- package/static/app/assets/{cpu-BIlWInHf.js → cpu-DFdo1gw-.js} +1 -1
- package/static/app/assets/{download-BtjXfL3z.js → download-SnJL6oqk.js} +1 -1
- package/static/app/assets/{folder-open-DefMpxI2.js → folder-open-CqZeDkjE.js} +1 -1
- package/static/app/assets/{hard-drive-BQ8NZVkw.js → hard-drive-j1jJXYYf.js} +1 -1
- package/static/app/assets/{index-vtEfYvQY.css → index-BLPb5lmE.css} +1 -1
- package/static/app/assets/index-_u5iUHDr.js +10 -0
- package/static/app/assets/input-B0lPdRQZ.js +1 -0
- package/static/app/assets/link-2-CoFbooHS.js +1 -0
- package/static/app/assets/{permissionCopy-BqZ5tsgL.js → permissionCopy-BsyLxtao.js} +1 -1
- package/static/app/assets/primitives-DEbN-d6p.js +1 -0
- package/static/app/assets/search-BybIWPNd.js +1 -0
- package/static/app/assets/{share-2-D5zg_0fY.js → share-2-CVtZ_ewX.js} +1 -1
- package/static/app/assets/{shield-alert-B5pZzkUb.js → shield-alert-CBi2GNWM.js} +1 -1
- package/static/app/assets/{textarea-nEVIweKY.js → textarea-DNMpB5ih.js} +1 -1
- package/static/app/assets/{useFocusTrap-Cm99AHlz.js → useFocusTrap-C83t3GXF.js} +1 -1
- package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
- package/static/app/assets/{useQuery-Dm__N6bL.js → useQuery-Dcp1OChy.js} +1 -1
- package/static/app/assets/utils-BlZr7Pd4.js +4 -0
- package/static/app/assets/workspace-jJY4RuAV.js +1 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/graph/_kg_common.py +0 -1331
- package/lattice_brain/graph/discovery_index.py +0 -1141
- package/lattice_brain/graph/retrieval.py +0 -1120
- package/lattice_brain/graph/retrieval_vector.py +0 -1293
- package/lattice_brain/ingestion.py +0 -1377
- package/lattice_brain/portability.py +0 -1210
- package/latticeai/core/agent.py +0 -1412
- package/latticeai/core/embedding_providers.py +0 -1196
- package/latticeai/core/file_generation.py +0 -1047
- package/latticeai/integrations/telegram_bot.py +0 -1390
- package/latticeai/models/router.py +0 -1007
- package/latticeai/runtime/build_phases.py +0 -1422
- package/latticeai/services/brain_intelligence.py +0 -967
- package/latticeai/services/memory_service.py +0 -1177
- package/latticeai/services/model_runtime.py +0 -1281
- package/latticeai/setup/wizard.py +0 -1277
- package/static/app/assets/Act-D0HWqtn0.js +0 -1
- package/static/app/assets/AdminConsole-D-QDW-A4.js +0 -1
- package/static/app/assets/BrainHome-Btns-_TA.js +0 -2
- package/static/app/assets/BrainSignals-2dHQNkns.js +0 -1
- package/static/app/assets/Capture-CT8v1StE.js +0 -1
- package/static/app/assets/CommandPalette-DoLXC2KH.js +0 -1
- package/static/app/assets/Library-DDoxFE5c.js +0 -1
- package/static/app/assets/ProductFlow-DOYf7JIs.js +0 -1
- package/static/app/assets/ReviewCard-COQsqidK.js +0 -3
- package/static/app/assets/System-BRllvYXd.js +0 -1
- package/static/app/assets/arrow-left-DnyMzss-.js +0 -1
- package/static/app/assets/brain-uMb_5hnO.js +0 -1
- package/static/app/assets/index-0AvoEBzJ.js +0 -10
- package/static/app/assets/input-B_5ZJ9oy.js +0 -1
- package/static/app/assets/primitives-CVwew78r.js +0 -1
- package/static/app/assets/search-DkhnOKZt.js +0 -1
- package/static/app/assets/utils-DcDMoZIe.js +0 -4
- package/static/app/assets/workspace-LtRRSKTf.js +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# v11.3.0 — Time Remembers (설계 문서)
|
|
2
|
+
|
|
3
|
+
> 상태: 계획 → 구현 → **출하됨(v11.3.0)**. 이 문서가 이번 릴리스의 설계
|
|
4
|
+
> 단일 출처다. 설계·기획: 본 문서. 구현: 트랙별 작업 노트 참조.
|
|
5
|
+
|
|
6
|
+
두 트랙을 하나의 릴리스로 출하한다.
|
|
7
|
+
|
|
8
|
+
- **트랙 A — No Big Files**: 1,000줄 이상인 1st-party 소스 파일 28개를
|
|
9
|
+
전부 응집 단위로 분해하고, 재발을 막는 라인 상한 게이트를 lint에 추가한다.
|
|
10
|
+
동작 변화 0, 화면 변화 0.
|
|
11
|
+
- **트랙 B — 기억의 연대기 (Brain Chronicle)**: v11.1.0이 심어 둔
|
|
12
|
+
bitemporal 데이터(`valid_from`/`valid_to`/`superseded_by`,
|
|
13
|
+
`edge_occurrences.observed_at`, `store.as_of()`)를 처음으로 사용자 앞에
|
|
14
|
+
꺼내는 7번째 주 화면. 두뇌가 자란 과정을 날짜로 보고, 임의 시점으로
|
|
15
|
+
되감는다.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 트랙 A — No Big Files
|
|
20
|
+
|
|
21
|
+
### 왜
|
|
22
|
+
|
|
23
|
+
`frontend/src/styles.css`는 10,956줄이다. 그 아래로도 1,000줄을 넘는
|
|
24
|
+
파일이 27개 있다. 리뷰가 느려지고, 에이전트 컨텍스트가 낭비되고, 충돌이
|
|
25
|
+
잦아진다. "누가 봐도 큰 파일"이 리포에 남지 않게 한다.
|
|
26
|
+
|
|
27
|
+
### 규칙
|
|
28
|
+
|
|
29
|
+
- **상한: 파일당 1,000줄** (git-추적 1st-party `*.py *.ts *.tsx *.js
|
|
30
|
+
*.mjs *.cjs *.css` 전부 — 테스트 포함).
|
|
31
|
+
- **제외(생성물/벤더)**: `frontend/src/api/openapi.ts`, `static/app/**`,
|
|
32
|
+
`static/vendor/**`, `node_modules`, `src-tauri/gen/**`.
|
|
33
|
+
- **게이트**: `scripts/check_max_file_lines.mjs`가 `npm run lint` 체인에
|
|
34
|
+
들어간다. 상한 초과 파일을 경로+줄수로 나열하며 실패한다. 제외 목록은
|
|
35
|
+
스크립트 안에 사유 주석과 함께 명시한다.
|
|
36
|
+
|
|
37
|
+
### 분해 대상 (28)
|
|
38
|
+
|
|
39
|
+
CSS/프론트: `styles.css`(10,956) `graph-home.css`(1,387)
|
|
40
|
+
`i18n/brain.ts`(1,647) `i18n/workspace.ts`(1,522)
|
|
41
|
+
`client.test.ts`(1,131) `useBrainChat.test.tsx`(1,037)
|
|
42
|
+
`vscode-extension/extension.ts`(1,104)
|
|
43
|
+
|
|
44
|
+
Python(lattice_brain): `ingestion.py`(1,525) `portability.py`(1,347)
|
|
45
|
+
`graph/_kg_common.py`(1,331) `graph/retrieval_vector.py`(1,293)
|
|
46
|
+
`multimodal.py`(1,258) `graph/discovery_index.py`(1,141)
|
|
47
|
+
`graph/retrieval.py`(1,120) `graph/projection.py`(1,008)
|
|
48
|
+
|
|
49
|
+
Python(latticeai): `core/agent.py`(1,465) `runtime/build_phases.py`(1,450)
|
|
50
|
+
`integrations/telegram_bot.py`(1,390) `setup/wizard.py`(1,310)
|
|
51
|
+
`services/model_runtime.py`(1,281) `core/embedding_providers.py`(1,196)
|
|
52
|
+
`services/memory_service.py`(1,177) `services/brain_intelligence.py`(1,083)
|
|
53
|
+
`core/file_generation.py`(1,047) `models/router.py`(1,007)
|
|
54
|
+
|
|
55
|
+
테스트/인프라: `tests/unit/test_layout_rebuild_apis.py`(1,685)
|
|
56
|
+
`tests/visual/v3.spec.js`(1,246) `tests/visual/mock_server.cjs`(1,226)
|
|
57
|
+
|
|
58
|
+
### 분해 프로토콜 (v10.0.1 교훈의 성문화)
|
|
59
|
+
|
|
60
|
+
**Python** — 모듈 → 동명 패키지:
|
|
61
|
+
|
|
62
|
+
1. `foo.py` → `foo/` 디렉터리. 내부를 응집 단위 서브모듈로 나눈다
|
|
63
|
+
(각 ≤1,000줄, 목표 ≤700줄).
|
|
64
|
+
2. `foo/__init__.py`가 **모든 공개 이름 + 테스트가 참조하는 모든 이름**을
|
|
65
|
+
재수출한다. 기존 `__all__` 유지. import 경로는 단 하나도 깨지지 않는다.
|
|
66
|
+
3. **monkeypatch 함정**: 테스트가 `patch("pkg.foo.bar")`로 패치하는
|
|
67
|
+
심볼은, `bar`를 사용하는 코드와 같은 서브모듈에 두거나 테스트의 패치
|
|
68
|
+
대상을 실제 위치로 갱신한다. 패키지 `__init__`에 재수출된 이름을
|
|
69
|
+
패치해도 서브모듈 내부의 전역 참조는 바뀌지 않는다.
|
|
70
|
+
4. **모듈 전역 상태**(싱글턴, 캐시, 레지스트리)는 정확히 한 서브모듈에만
|
|
71
|
+
존재한다. 순환은 의존 순서 정리로 풀고, 문자열 리터럴 지연 import로
|
|
72
|
+
우회하지 않는다.
|
|
73
|
+
5. **AST 동등성 증명**: 분해 전(`git show HEAD:<path>`)과 후(서브모듈
|
|
74
|
+
합집합)의 top-level 함수/클래스 각각에 대해 `ast.dump` 비교. import
|
|
75
|
+
문과 위치 이동 외 차이 0을 확인하고 결과를 작업 노트에 남긴다.
|
|
76
|
+
6. `pyproject.toml`의 mypy `files` 목록을 새 경로로 갱신한다(원 모듈이
|
|
77
|
+
채택돼 있던 경우 서브모듈 전부 채택).
|
|
78
|
+
|
|
79
|
+
**CSS** — `styles.css`는 이미 104개 섹션 주석으로 구조화되어 있다.
|
|
80
|
+
섹션 묶음별로 `frontend/src/styles/core/*.css`로 옮기고 `styles.css`
|
|
81
|
+
상단에서 원래 순서 그대로 `@import`한다. Tailwind v4는 unlayered 규칙이
|
|
82
|
+
layered 유틸을 항상 이기므로 캐스케이드는 순서 보존이 전부다.
|
|
83
|
+
**증명: 분해 전후 `vite build` 산출 CSS 번들이 동일해야 한다**
|
|
84
|
+
(해시 제외 byte-diff).
|
|
85
|
+
|
|
86
|
+
**i18n** — `brain.ts`/`workspace.ts`를 도메인별 사전 파일로 나누고 기존
|
|
87
|
+
파일이 병합해 `registerCopy` 한 번만 호출한다. ko/en 키 패리티 테스트와
|
|
88
|
+
`check_i18n_namespace_coverage.mjs`가 그대로 통과해야 한다.
|
|
89
|
+
`vitest.config.ts`의 커버리지 제외 패턴을 `frontend/src/i18n/**`로
|
|
90
|
+
넓힌다(사전은 로직이 아니다 — 기존 사유 그대로).
|
|
91
|
+
|
|
92
|
+
**검증 플로어(불변)**: Python 문·분기 100.00% (`fail_under = 100`),
|
|
93
|
+
프론트 4지표 100%, mypy 0 에러, ruff 클린, `npm run lint` 전 서브게이트,
|
|
94
|
+
전 테스트 그린. 분해는 이 플로어 위에서만 성립한다.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 트랙 B — 기억의 연대기 (Brain Chronicle)
|
|
99
|
+
|
|
100
|
+
### 왜 이것인가
|
|
101
|
+
|
|
102
|
+
v11.1.0이 노드·엣지에 `valid_from`/`valid_to`/`superseded_by`를 심고
|
|
103
|
+
`edge_occurrences(observed_at)`와 `store.as_of()`/`neighbors(as_of=)`까지
|
|
104
|
+
구현했지만, **이 데이터를 노출하는 HTTP 엔드포인트도, 화면도 0개다.**
|
|
105
|
+
"두뇌가 시간을 기억한다"는 이 제품의 약속이 아직 보이지 않는다.
|
|
106
|
+
연대기는 그 약속을 첫 화면급 표면으로 만든다. 전부 읽기 전용이라
|
|
107
|
+
기존 시스템에 지장이 없다.
|
|
108
|
+
|
|
109
|
+
### 사용자에게 보이는 것
|
|
110
|
+
|
|
111
|
+
새 주 화면 **연대기** (라우트 id `chronicle`, 해시 `#/chronicle`,
|
|
112
|
+
everyday 내비 4번째 항목). 위에서 아래로:
|
|
113
|
+
|
|
114
|
+
1. **성장 곡선 + 스크러버** — 누적 기억(자료·개념·연결·대화)의 SVG 영역
|
|
115
|
+
차트. 핸들을 잡고 끌면 아래 전체가 그 시점으로 따라온다.
|
|
116
|
+
2. **활동 히트맵** — 주×요일 격자, 하루의 활동 밀도. 클릭하면 그 날로.
|
|
117
|
+
3. **그날의 이야기** — 선택한 날짜에 두뇌에 들어온 것들을 평이한 한국어
|
|
118
|
+
그룹 카드로: 자료(제목·출처), 새로 생긴 개념, 나눈 대화(첫 줄 미리보기),
|
|
119
|
+
달라진 사실(superseded/모순 해소). 각 카드는 기존 화면(기억 검색,
|
|
120
|
+
그래프, 대화)으로 연결된다.
|
|
121
|
+
4. **그때의 두뇌 (되감기)** — 스크러버가 과거에 있을 때: 그 시점의
|
|
122
|
+
노드·연결 수와 그때 중요했던 개념 목록. `as_of` 읽기의 첫 노출.
|
|
123
|
+
|
|
124
|
+
빈 두뇌엔 정직한 빈 상태: "오늘부터 기록이 쌓입니다." 요약 생성이나
|
|
125
|
+
모델 호출은 없다 — 전부 이미 저장된 사실의 재배열이다(정직성 원칙).
|
|
126
|
+
|
|
127
|
+
### API 계약 (신규 라우터, 전부 read-only)
|
|
128
|
+
|
|
129
|
+
`latticeai/api/chronicle.py` — router-factory 규약(전체 경로, prefix 없음),
|
|
130
|
+
`require_user` + `gate_read`, 워크스페이스 스코프. 서비스는
|
|
131
|
+
`latticeai/services/chronicle.py`의 `ChronicleService`(순수 SQLite 읽기,
|
|
132
|
+
날짜 버킷은 `latticeai/core/timezones.py` 기준).
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
GET /api/chronicle/overview
|
|
136
|
+
→ { first_activity_at, last_activity_at,
|
|
137
|
+
totals: { sources, entities, connections, conversations },
|
|
138
|
+
series: [ { date, sources, entities, connections, conversations } ] }
|
|
139
|
+
|
|
140
|
+
GET /api/chronicle/day/{date} # YYYY-MM-DD, LATTICE_TZ 기준
|
|
141
|
+
→ { date, counts, groups: {
|
|
142
|
+
sources: [ { id, title, source_type, captured_at, node_id } ],
|
|
143
|
+
entities: [ { id, label, type, created_at } ],
|
|
144
|
+
conversations: [ { conversation_id, preview, messages, started_at } ],
|
|
145
|
+
changes: [ { kind, label, at, node_id } ] } }
|
|
146
|
+
|
|
147
|
+
GET /api/chronicle/as-of?ts=<ISO8601>
|
|
148
|
+
→ { ts, stats: { entities, connections },
|
|
149
|
+
top_entities: [ { id, label, type, importance_score } ] }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
데이터 원천: `ingestion_provenance(created_at, captured_at)`,
|
|
153
|
+
`nodes_v2`/`edges_v2`(`created_at`, temporal 3열),
|
|
154
|
+
`conversation_messages(timestamp)`, `edge_occurrences(observed_at)`.
|
|
155
|
+
`as-of`는 `store.as_of()` 계열을 통해서만 읽는다(중복 구현 금지).
|
|
156
|
+
등록은 `build_phases.py` platform_features 단계에 직접 include.
|
|
157
|
+
스키마 변경 0, 쓰기 0.
|
|
158
|
+
|
|
159
|
+
### 프론트 구조
|
|
160
|
+
|
|
161
|
+
- `frontend/src/pages/Chronicle.tsx` + `frontend/src/features/chronicle/`
|
|
162
|
+
(GrowthScrubber / ActivityHeatmap / DayStory / RewindPanel — 각 ≤1,000줄).
|
|
163
|
+
- `routes.ts`: `productShellRoutes` 7번째 항목, `directProductRoutes`,
|
|
164
|
+
`commandRoutes`(팔레트), 별칭 `timeline` → `chronicle`.
|
|
165
|
+
- `App.tsx`: lazy import + everyday 내비(대화·자료·기억·연대기).
|
|
166
|
+
- i18n: 신규 네임스페이스 `frontend/src/i18n/chronicle.ts`
|
|
167
|
+
(ko/en 패리티, 페이지 청크에서 등록). basic 모드 어휘: 개발 용어 금지.
|
|
168
|
+
- `api/client.ts`에 typed 메서드 3개. OpenAPI 재생성으로 타입 공급.
|
|
169
|
+
- 시각 회귀: `tests/visual/mock_server.cjs`에 chronicle 3 엔드포인트
|
|
170
|
+
픽스처(채워진 상태) 추가 — 릴리스 스크린샷이 빈 화면을 찍지 않게 한다.
|
|
171
|
+
|
|
172
|
+
### 테스트
|
|
173
|
+
|
|
174
|
+
- 백엔드: 서비스 단위(빈 두뇌·단일 일자·다일자·workspace 경계·tz 경계·
|
|
175
|
+
as-of 과거/미래/경계)와 라우터(인증·게이트·404·검증 실패)를 실제
|
|
176
|
+
tmp_path SQLite 스토어로. 문·분기 100%.
|
|
177
|
+
- 프론트: 신규 파일 4지표 100%. routes/parseHash, 팔레트 노출,
|
|
178
|
+
스크러버 드래그 → as-of 반영, 빈 상태.
|
|
179
|
+
|
|
180
|
+
### 릴리스 증거
|
|
181
|
+
|
|
182
|
+
- 신규 스크린샷 `13-chronicle.png` 캡처 목록 추가, README 기능 표에
|
|
183
|
+
연대기 카드 추가.
|
|
184
|
+
- 내비가 화면 전체에 4번째 everyday 항목을 추가하므로
|
|
185
|
+
`scripts/release_screen_claims.json`의 11.3.0 항목은 topbar가 보이는
|
|
186
|
+
화면들을 claim한다.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 릴리스 체크리스트 (11.3.0)
|
|
191
|
+
|
|
192
|
+
1. 트랙 A → 트랙 B 순서로 구현(연대기가 분해된 `build_phases` 위에 얹힘).
|
|
193
|
+
2. 전 게이트 로컬 그린 → fresh venv 재검증(coverage-100 교훈).
|
|
194
|
+
3. `bump_version.py 11.3.0` → OpenAPI 재생성 → 문서 갱신
|
|
195
|
+
(`check_current_release_docs.mjs`의 releaseTheme = "Time Remembers"
|
|
196
|
+
갱신 포함 — README/RELEASE.md/RELEASE_NOTES.md 제목과 일치 필수).
|
|
197
|
+
4. `npm run release:evidence` (스크린샷 12+1, walkthrough GIF).
|
|
198
|
+
5. `npm run release:artifacts` → whl · tar.gz · tgz · vsix ·
|
|
199
|
+
**DMG**(`Lattice AI_11.3.0_aarch64.dmg`) → `release:validate`
|
|
200
|
+
(`--require-vsix --require-tgz --require-dmg`) → `release:smoke`.
|
|
201
|
+
6. main 커밋·푸시 → 태그 `v11.3.0` → GitHub Release(정확한 파일명,
|
|
202
|
+
와일드카드 금지) → main CI 4종(CI·Visual·Sidecar·Audit) 그린 확인.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# v11.4.0 — Rust Foundation (설계 문서)
|
|
2
|
+
|
|
3
|
+
> 상태: 계획 → 구현 → **출하됨(v11.4.0)**. Rust 전환의 단일 출처 설계
|
|
4
|
+
> 문서. 설계·기획: 본 문서. 목표 아키텍처는 아래 다이어그램, 이번
|
|
5
|
+
> 릴리스는 그 **Phase 1**이다.
|
|
6
|
+
|
|
7
|
+
## 목표 아키텍처 (전환 완료 시점의 모습)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Desktop(Tauri) Browser Ext VS Code Ext
|
|
11
|
+
└────────────────┼────────────────┘
|
|
12
|
+
React / TypeScript
|
|
13
|
+
│
|
|
14
|
+
┌───────▼───────┐
|
|
15
|
+
│ Lattice Host │ Rust
|
|
16
|
+
│ (supervisor + │
|
|
17
|
+
│ IPC/API 관문) │
|
|
18
|
+
└───────┬───────┘
|
|
19
|
+
┌────────────────────┼────────────────────┐
|
|
20
|
+
lattice-core Agent Runtime IPC / API
|
|
21
|
+
(Brain·Graph· (Tools·Sandbox· (gateway,
|
|
22
|
+
Memory·Storage) Permission·Jobs) native routes)
|
|
23
|
+
│ │
|
|
24
|
+
Ingestion ──── Retrieval(Vector·BM25·KG → Fusion → Rerank
|
|
25
|
+
│ → Context Builder)
|
|
26
|
+
└──────────────┬──────────────┘
|
|
27
|
+
▼
|
|
28
|
+
Python AI Worker ← 최종적으로 Python이 맡는 유일한 층
|
|
29
|
+
(MLX · Ollama · Cloud → LLM, chat/agent 추론)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 왜 단계적인가 (정직성 원칙)
|
|
33
|
+
|
|
34
|
+
현 Python 플랫폼은 44개 라우터 파일 · ~448 라우트 · 40,488문 100.00%
|
|
35
|
+
커버리지 플로어 위에 있다. 이것을 한 릴리스에 재작성하는 것은 "전체
|
|
36
|
+
시스템에 지장 없음"과 양립할 수 없다. 그래서 각 릴리스가 **실제로
|
|
37
|
+
작동하고 증명된 Rust 조각**을 늘려가고, Python은 마지막에 AI Worker
|
|
38
|
+
(모델 추론)로 수렴한다. 11.4.0은 그 뼈대를 세운다.
|
|
39
|
+
|
|
40
|
+
## Phase 1 스코프 (11.4.0)
|
|
41
|
+
|
|
42
|
+
`rust/` cargo workspace 신설 — 3개 크레이트, 전부 tauri 비의존(우분투
|
|
43
|
+
CI에서 시스템 패키지 없이 테스트 가능):
|
|
44
|
+
|
|
45
|
+
### 1. `lattice-core` — Brain 저장소 읽기 층 + 임베딩 산술
|
|
46
|
+
|
|
47
|
+
- 데이터 디렉터리 해석: `LATTICEAI_DATA_DIR` → 기본 `~/.ltcai`,
|
|
48
|
+
DB는 `knowledge_graph.sqlite` (Python과 동일 파일).
|
|
49
|
+
- 읽기 전용 SQLite 접속(rusqlite `bundled` + **fts5 필수**): WAL,
|
|
50
|
+
`busy_timeout` 명시(Python은 미설정 — Rust가 양보하는 쪽).
|
|
51
|
+
- 스키마 리더: `kgv2_nodes`/`kgv2_edges` 뷰(어휘·그래프 lane),
|
|
52
|
+
`nodes`/`chunks`/`vector_embeddings`(벡터 lane),
|
|
53
|
+
`nodes_v2`(워크스페이스 스코핑) — **lane별 테이블 이원화는 Python
|
|
54
|
+
현행 그대로** 재현한다.
|
|
55
|
+
- 임베딩 모듈 = `lattice_brain/embeddings.py`의 1:1 포트:
|
|
56
|
+
토크나이저(`[a-z0-9][a-z0-9_.:/+-]{1,}` + latin 3-gram(len≥5) +
|
|
57
|
+
한국어 2-gram(len≥3)), blake2b-8 해시 bag-of-features(하위 비트 부호),
|
|
58
|
+
L2 정규화, `<f32` LE 인코딩/디코딩(4B/dim, 헤더 없음), 유사도는
|
|
59
|
+
**순수 내적**(dim 불일치는 하드 에러). 기본 dim 384,
|
|
60
|
+
모델 id `lattice-local-hash-v1:{dim}`.
|
|
61
|
+
|
|
62
|
+
### 2. `lattice-retrieval` — 네이티브 하이브리드 검색 (패리티 증명 동반)
|
|
63
|
+
|
|
64
|
+
패리티 대상은 **graph-layer `KnowledgeGraphStore.hybrid_search`**
|
|
65
|
+
(2채널 alpha 융합)와 그 구성 lane인 `search()`(키워드),
|
|
66
|
+
`vector_search()`(벡터). 서비스 층 3채널 융합(`SearchService`)은
|
|
67
|
+
traverse/relationship 의존이 커서 Phase 2로 명시 이월.
|
|
68
|
+
|
|
69
|
+
기본 env 구성(brute 백엔드, RRF/그래프 확장/이미지 융합/CE rerank 꺼짐)
|
|
70
|
+
에서 다음을 정확 재현:
|
|
71
|
+
|
|
72
|
+
- **키워드 lane**: trigram FTS5 `MATCH '"…"'`(내부 `"` 이중화,
|
|
73
|
+
len<3 → 빈 결과) → LIKE 폴백(`%q%`, `updated_at DESC, id ASC`) →
|
|
74
|
+
topic-term 보충(`limit*3`) → **(hits, type_boost, updated_at) 재점수**
|
|
75
|
+
(hits = topic 후보 12개의 대소문자 무시 부분일치 수, type_boost
|
|
76
|
+
집합 고정) + **2-pass 안정 정렬(id ASC → score DESC)**. 점수는
|
|
77
|
+
호출측 1/rank.
|
|
78
|
+
- **벡터 lane**: `embedding_model+dim` 필터, `indexed_at DESC` 후보
|
|
79
|
+
상한(기본 10000, `LATTICEAI_VECTOR_MAX_CANDIDATES`), 순차 f64 내적
|
|
80
|
+
(Python과 동일 누적 순서 → bit-exact), min_score 필터,
|
|
81
|
+
`(score, updated_at) DESC` 안정 정렬, recall/honesty 필드 동일.
|
|
82
|
+
- **정책**: `classify_query`(ko/en 정규식, code→recency→person→fact
|
|
83
|
+
우선순위)는 **원 질의**에, rewrite(공백 축약 + filler 3규칙,
|
|
84
|
+
remainder≥4)는 검색 질의에. recency 클래스만 half-life 14일 감쇠
|
|
85
|
+
`0.5 + 0.5*recency`.
|
|
86
|
+
- **융합**: lexical 1/rank, vector max-정규화, `alpha*vec+(1-α)*lex`,
|
|
87
|
+
`round(x,6)`(**banker's rounding** — CPython round와 동치 구현),
|
|
88
|
+
정렬 `(-score, node_id)`, identity rerank(`rerank_score=score`,
|
|
89
|
+
top_k 절단), 결과 JSON 필드·라벨(`fusion`, `sources`, `policy`,
|
|
90
|
+
`vector.*`) 전부 동형.
|
|
91
|
+
- **스코핑**: `allowed_workspaces=None` ⇒ 스코핑 없음(로컬 신뢰 소유자
|
|
92
|
+
기본 경로) — fail-closed로 바꾸지 않는다. 빈 집합 ⇒ 아무것도 매치
|
|
93
|
+
안 됨. legacy-global(NULL)은 `include_legacy_global`로만 포함.
|
|
94
|
+
|
|
95
|
+
**패리티 하네스(양방향 계약)**:
|
|
96
|
+
- `scripts/generate_rust_parity_fixtures.py`(Python)가 실제 Python
|
|
97
|
+
쓰기 경로로 소형 스토어를 빚어 `rust/fixtures/parity_store.sqlite`
|
|
98
|
+
+ 질의 세트(ko/en, 4 클래스, len<3, 무결과, 동점 tie) +
|
|
99
|
+
`rust/fixtures/golden/*.json`(Python 엔진의 hybrid/keyword/vector
|
|
100
|
+
출력)을 커밋 산출물로 생성.
|
|
101
|
+
- `tests/unit/test_rust_parity_contract.py`: 커밋된 fixture DB에 대해
|
|
102
|
+
Python 엔진을 다시 돌려 golden과 일치함을 상시 단언 — Python 쪽
|
|
103
|
+
의미 변경이 골든을 조용히 무효화하지 못한다.
|
|
104
|
+
- `lattice-retrieval`의 cargo 테스트: 같은 DB·같은 질의에 대해 Rust
|
|
105
|
+
출력 == 같은 golden. 점수는 기본 완전 일치(불가피한 tie 케이스만
|
|
106
|
+
1e-12, 사유 주석).
|
|
107
|
+
|
|
108
|
+
### 3. `lattice-host` — 수퍼바이저 + IPC/API 게이트웨이
|
|
109
|
+
|
|
110
|
+
- **수퍼바이저(lib + bin)**: Python 워커 스폰(명령 해석은 현행 Tauri
|
|
111
|
+
규칙 승계: `LATTICEAI_DESKTOP_BACKEND_CMD` → `ltcai` → python 후보 →
|
|
112
|
+
번들 트리; 현행 `sort()+dedup()`이 우선순위를 파괴하던 버그는 수정),
|
|
113
|
+
**`/health` HTTP 프로브**(현행 TCP 접속 프로브 대체), 크래시 시
|
|
114
|
+
지수 백오프 자동 재시작(상한·수동 정지 존중), 우아한 종료(SIGTERM →
|
|
115
|
+
유예 → SIGKILL), **포트 통일**: 기본 4825에서 시작해 빈 포트 탐색,
|
|
116
|
+
선택 포트를 워커 env(`LATTICEAI_PORT`)와 상위(Tauri `backend_origin`)
|
|
117
|
+
에 단일 출처로 공급. 로그는 기존 `~/.ltcai/desktop-sidecar.log` 계승.
|
|
118
|
+
- **게이트웨이(axum, 127.0.0.1 전용)**: `/host/health`·`/host/status`
|
|
119
|
+
(수퍼바이저 상태), 네이티브 `/rust/search/{hybrid,keyword,vector}`
|
|
120
|
+
(lattice-retrieval 직결, 읽기 전용), 그 외 전 경로는 워커로 리버스
|
|
121
|
+
프록시(스트리밍/SSE 통과). Phase 1에서는 **옵트인 front-door**
|
|
122
|
+
(`lattice-host` 바이너리 실행 시) — 기존 진입 경로(ltcai CLI,
|
|
123
|
+
브라우저 직접 접속)는 그대로 둔다. 비-loopback 바인드 거부.
|
|
124
|
+
|
|
125
|
+
### 4. Tauri 통합 (DMG가 새 기초 위에 올라탐)
|
|
126
|
+
|
|
127
|
+
`src-tauri`는 `lattice-host`를 path 의존으로 소비: `main.rs`의 ad-hoc
|
|
128
|
+
스폰/프로브/재시작 로직을 크레이트 호출로 대체(451줄 → 얇은 셸).
|
|
129
|
+
Tauri 명령 5종(`backend_origin`/`backend_status`/`restart_backend`/
|
|
130
|
+
`shutdown_backend`/`select_folder`)의 프론트 계약은 불변. 네트워크
|
|
131
|
+
토폴로지도 불변(웹뷰는 여전히 `{origin}/app`) — 사용자 가시 변화 없이
|
|
132
|
+
수퍼바이저 품질만 오른다(HTTP 헬스, 자동 재시작, 포트 충돌 처리).
|
|
133
|
+
|
|
134
|
+
### 5. 게이트/CI/버전
|
|
135
|
+
|
|
136
|
+
- ci.yml에 `rust` 잡 신설: ubuntu, stable 툴체인(핀), `cargo fmt
|
|
137
|
+
--check` + `clippy -D warnings` + `cargo test` — `rust/` workspace만
|
|
138
|
+
(tauri 시스템 의존 없음). src-tauri 빌드는 종전대로 릴리스 플로우
|
|
139
|
+
(macOS)에서.
|
|
140
|
+
- `check_max_file_lines.mjs`에 `*.rs` 추가(상한 1,000, `src-tauri/gen`
|
|
141
|
+
·`target` 제외). 모든 신규 Rust 파일 목표 ≤500줄.
|
|
142
|
+
- `scripts/bump_version.py`와 `test_version_consistency.py`에
|
|
143
|
+
`rust/Cargo.toml`(workspace.package.version, 크레이트는
|
|
144
|
+
`version.workspace = true`) 편입 — 버전 단일 출처 유지.
|
|
145
|
+
- Python 커버리지 플로어(문·분기 100%)와 프론트 100%는 그대로. Rust는
|
|
146
|
+
cargo 테스트 + 패리티 골든이 게이트.
|
|
147
|
+
|
|
148
|
+
### 명시적 비-목표 (Phase 1에서 하지 않는 것)
|
|
149
|
+
|
|
150
|
+
- 3채널 `SearchService.hybrid_search`/KG 읽기 API의 네이티브화(Phase 2),
|
|
151
|
+
- Ingestion(Watch/Parse/Chunk/Dedup)·Jobs/Events/Scheduler(Phase 2~3),
|
|
152
|
+
- Agent Runtime(Tools/Sandbox/Permission)의 Rust화(Phase 4),
|
|
153
|
+
- 게이트웨이의 기본 front-door 승격(Phase 2에서 e2e로 검증 후),
|
|
154
|
+
- HNSW/quantized 백엔드·CE rerank·이미지 late fusion 패리티(브루트
|
|
155
|
+
기본 경로만; 나머지는 요청 시 워커로 프록시되므로 기능 손실 없음).
|
|
156
|
+
|
|
157
|
+
## 로드맵 (다이어그램 → 릴리스 매핑)
|
|
158
|
+
|
|
159
|
+
| Phase | 릴리스 | 내용 |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| 1 | **11.4.0** | workspace + core 읽기층 + retrieval 패리티 + host 수퍼바이저/게이트웨이(옵트인) + Tauri 통합 |
|
|
162
|
+
| 2 | 11.5.x | 게이트웨이 front-door 기본화, KG 읽기·3채널 검색·history 네이티브, Ingestion Watch/Chunk/Dedup |
|
|
163
|
+
| 3 | 11.6.x | Jobs/Events/Pipeline/Scheduler, Context Builder 네이티브 |
|
|
164
|
+
| 4 | 12.0 | Agent Runtime(Tools·Process·Sandbox·Permission) Rust화 — Python은 AI Worker로 수렴 |
|
|
165
|
+
|
|
166
|
+
## 릴리스 체크리스트 (11.4.0)
|
|
167
|
+
|
|
168
|
+
1. W1(host) ∥ W2(core+retrieval+패리티) → W3(Tauri·CI·게이트·버전 배선).
|
|
169
|
+
2. 전 게이트 로컬 그린(pytest 100% 플로어·프론트·lint 체인·cargo 3종)
|
|
170
|
+
+ fresh venv 재검증.
|
|
171
|
+
3. `bump_version.py 11.4.0`(rust workspace 포함) → OpenAPI 재생성 →
|
|
172
|
+
문서·releaseTheme("Rust Foundation") → claims(화면 무변경) →
|
|
173
|
+
`release:evidence` 재촬영(스크린샷 13종 + GIF) →
|
|
174
|
+
`release:artifacts`(DMG 포함) → validate/smoke.
|
|
175
|
+
4. 브랜치 `release/v11.4.0` 커밋·푸시 → PR로 CI 그린 → 태그
|
|
176
|
+
`v11.4.0` → GitHub Release(정확한 파일명 5종).
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Opt-in gates that can still be answered at runtime (v11.2.0).
|
|
2
|
+
|
|
3
|
+
Every opt-in feature in this product used to decide once, in a constructor:
|
|
4
|
+
``self._on = os.getenv("LATTICEAI_…") in {"1", …}``. That is correct for a
|
|
5
|
+
process that reads its environment at boot and never changes its mind, and it
|
|
6
|
+
is a dead end for the settings screen that is coming — a UI toggle cannot move
|
|
7
|
+
a boolean that was already copied into ``self``.
|
|
8
|
+
|
|
9
|
+
:class:`FeatureGate` is the seam that keeps both true at once. It answers at
|
|
10
|
+
*call* time in a fixed order:
|
|
11
|
+
|
|
12
|
+
1. a **bound resolver** — a callable the app layer supplies (the future
|
|
13
|
+
settings surface, a per-workspace policy, a test double);
|
|
14
|
+
2. an explicit **override** set through :meth:`FeatureGate.set`;
|
|
15
|
+
3. the **environment variable**, parsed exactly the way the hand-written
|
|
16
|
+
``os.getenv`` checks it replaces did;
|
|
17
|
+
4. the declared **default**.
|
|
18
|
+
|
|
19
|
+
An untouched gate therefore behaves identically to the frozen read it replaced
|
|
20
|
+
— same env var, same truthy words, same default — while a bound resolver wins
|
|
21
|
+
without a single change at any construction site. Brain Core owns this because
|
|
22
|
+
Brain Core owns the gates that matter most (multi-modal routing, sharing), and
|
|
23
|
+
it may not import ``latticeai``.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
from typing import Callable, Dict, Optional
|
|
30
|
+
|
|
31
|
+
#: The words this product has always accepted for "on" / "off".
|
|
32
|
+
TRUTHY = frozenset({"1", "true", "yes", "on"})
|
|
33
|
+
FALSY = frozenset({"0", "false", "no", "off"})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FeatureGate:
|
|
37
|
+
"""One opt-in switch, resolved when it is asked rather than when it is built."""
|
|
38
|
+
|
|
39
|
+
__slots__ = ("env_var", "default", "name", "detail", "_override", "_resolver")
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
env_var: str,
|
|
44
|
+
*,
|
|
45
|
+
default: bool = False,
|
|
46
|
+
name: str = "",
|
|
47
|
+
detail: str = "",
|
|
48
|
+
) -> None:
|
|
49
|
+
self.env_var = env_var
|
|
50
|
+
self.default = bool(default)
|
|
51
|
+
self.name = name or env_var
|
|
52
|
+
#: Plain sentence for a surface that has to explain why a feature is off.
|
|
53
|
+
self.detail = detail
|
|
54
|
+
self._override: Optional[bool] = None
|
|
55
|
+
self._resolver: Optional[Callable[[], bool]] = None
|
|
56
|
+
|
|
57
|
+
# ── resolution ───────────────────────────────────────────────────────────
|
|
58
|
+
def __call__(self) -> bool:
|
|
59
|
+
return self.enabled()
|
|
60
|
+
|
|
61
|
+
def enabled(self) -> bool:
|
|
62
|
+
"""The gate's answer *now* (resolver → override → env → default)."""
|
|
63
|
+
if self._resolver is not None:
|
|
64
|
+
return bool(self._resolver())
|
|
65
|
+
return self.local()
|
|
66
|
+
|
|
67
|
+
def local(self) -> bool:
|
|
68
|
+
"""This gate's own answer, ignoring any bound resolver.
|
|
69
|
+
|
|
70
|
+
The lower three layers on their own (override → env → default). A
|
|
71
|
+
resolver that only has an opinion *sometimes* — a settings service that
|
|
72
|
+
speaks for the features a person actually touched, and stays quiet about
|
|
73
|
+
the rest — hands the question back here, so an operator's environment
|
|
74
|
+
variable keeps working for everything nobody has decided.
|
|
75
|
+
"""
|
|
76
|
+
if self._override is not None:
|
|
77
|
+
return self._override
|
|
78
|
+
return self.from_env()
|
|
79
|
+
|
|
80
|
+
def from_env(self) -> bool:
|
|
81
|
+
"""The environment's answer alone, ignoring resolver and override."""
|
|
82
|
+
raw = os.getenv(self.env_var, "").strip().lower()
|
|
83
|
+
if raw in TRUTHY:
|
|
84
|
+
return True
|
|
85
|
+
if raw in FALSY:
|
|
86
|
+
return False
|
|
87
|
+
return self.default
|
|
88
|
+
|
|
89
|
+
def source(self) -> str:
|
|
90
|
+
"""Which of the four layers produced the current answer."""
|
|
91
|
+
if self._resolver is not None:
|
|
92
|
+
return "resolver"
|
|
93
|
+
if self._override is not None:
|
|
94
|
+
return "override"
|
|
95
|
+
if os.getenv(self.env_var, "").strip().lower() in (TRUTHY | FALSY):
|
|
96
|
+
return "env"
|
|
97
|
+
return "default"
|
|
98
|
+
|
|
99
|
+
# ── injection ────────────────────────────────────────────────────────────
|
|
100
|
+
def set(self, value: Optional[bool]) -> None:
|
|
101
|
+
"""Explicit runtime override. ``None`` hands the answer back to the env."""
|
|
102
|
+
self._override = None if value is None else bool(value)
|
|
103
|
+
|
|
104
|
+
def bind(self, resolver: Optional[Callable[[], bool]]) -> None:
|
|
105
|
+
"""Delegate the answer to a caller-supplied callable (``None`` unbinds)."""
|
|
106
|
+
self._resolver = resolver
|
|
107
|
+
|
|
108
|
+
def reset(self) -> None:
|
|
109
|
+
"""Forget both injections — the gate is env-driven again."""
|
|
110
|
+
self._override = None
|
|
111
|
+
self._resolver = None
|
|
112
|
+
|
|
113
|
+
def describe(self) -> Dict[str, object]:
|
|
114
|
+
"""Honest read for a status surface: state *and* where it came from."""
|
|
115
|
+
return {
|
|
116
|
+
"name": self.name,
|
|
117
|
+
"flag": self.env_var,
|
|
118
|
+
"enabled": self.enabled(),
|
|
119
|
+
"default": self.default,
|
|
120
|
+
"source": self.source(),
|
|
121
|
+
"detail": self.detail,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
__all__ = ["FALSY", "TRUTHY", "FeatureGate"]
|