ltcai 11.4.0 → 11.5.1

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.
Files changed (86) hide show
  1. package/README.md +55 -44
  2. package/docs/CHANGELOG.md +48 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +1 -1
  7. package/docs/TRUST_MODEL.md +1 -1
  8. package/docs/WHY_LATTICE.md +1 -1
  9. package/docs/kg-schema.md +1 -1
  10. package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +11 -6
  11. package/docs/v11.5.0_RUST_COMPLETE_PLAN.md +145 -0
  12. package/docs/v11.5.1_RUST_FULL_LOOP_PLAN.md +85 -0
  13. package/lattice_brain/__init__.py +1 -1
  14. package/lattice_brain/runtime/multi_agent.py +1 -1
  15. package/latticeai/__init__.py +1 -1
  16. package/latticeai/api/agent_worker_seam.py +389 -0
  17. package/latticeai/api/index_jobs.py +145 -0
  18. package/latticeai/core/legacy_compatibility.py +1 -1
  19. package/latticeai/core/marketplace.py +1 -1
  20. package/latticeai/core/messages.py +40 -0
  21. package/latticeai/core/workspace_os_constants.py +1 -1
  22. package/latticeai/runtime/build_phases/features.py +37 -2
  23. package/latticeai/services/architecture_readiness.py +1 -1
  24. package/latticeai/services/product_readiness.py +1 -1
  25. package/package.json +1 -1
  26. package/scripts/check_current_release_docs.mjs +1 -1
  27. package/scripts/check_server_i18n.mjs +2 -0
  28. package/scripts/chunking_parity_corpus.py +449 -0
  29. package/scripts/generate_agent_loop_fixtures.py +907 -0
  30. package/scripts/generate_agent_parity_fixtures.py +752 -0
  31. package/scripts/generate_chunking_parity_fixtures.py +259 -0
  32. package/scripts/generate_rust_parity_fixtures.py +541 -103
  33. package/scripts/parity_fixture_corpus_docgen.py +341 -0
  34. package/scripts/release_screen_claims.json +21 -0
  35. package/src-tauri/Cargo.lock +53 -4
  36. package/src-tauri/Cargo.toml +11 -4
  37. package/src-tauri/src/backend.rs +251 -140
  38. package/src-tauri/src/main.rs +16 -4
  39. package/src-tauri/src/topology.rs +356 -0
  40. package/src-tauri/tauri.conf.json +1 -1
  41. package/static/app/asset-manifest.json +41 -41
  42. package/static/app/assets/{Act-yYpYnn0v.js → Act-Drs_jd-O.js} +1 -1
  43. package/static/app/assets/{AdminConsole-DL3Cr5pL.js → AdminConsole-BqNx5oF6.js} +1 -1
  44. package/static/app/assets/{Brain-C1HBN0Wf.js → Brain-C7bdNmz-.js} +1 -1
  45. package/static/app/assets/{BrainHome-DoXRhUUC.js → BrainHome-DcTnINcI.js} +1 -1
  46. package/static/app/assets/{BrainSignals-6yR6ir5t.js → BrainSignals-Df1MRIPD.js} +1 -1
  47. package/static/app/assets/{Capture-CFIRsFNE.js → Capture-Dgsxvnbx.js} +1 -1
  48. package/static/app/assets/{Chronicle-BZbEgiwN.js → Chronicle-BrkTxJK1.js} +1 -1
  49. package/static/app/assets/{CommandPalette-D2pMxC2I.js → CommandPalette-DDziw7Oj.js} +1 -1
  50. package/static/app/assets/{Library-DwO3yZST.js → Library-CYxeuraA.js} +1 -1
  51. package/static/app/assets/{LivingBrain-Jn1GK0-S.js → LivingBrain-CNp6mvCm.js} +1 -1
  52. package/static/app/assets/{ProductFlow-B-w1R4Oo.js → ProductFlow-BVsowu3Z.js} +1 -1
  53. package/static/app/assets/{ReviewCard-6B27X8Vg.js → ReviewCard-D1LbOfJS.js} +1 -1
  54. package/static/app/assets/{System-DW8F-2xL.js → System-DcRAq7wj.js} +1 -1
  55. package/static/app/assets/arrow-left-BHYmOTWU.js +1 -0
  56. package/static/app/assets/{bot-IM_E_Y12.js → bot-BK2xQ9mN.js} +1 -1
  57. package/static/app/assets/{brain-Ci1CkWjM.js → brain-BZNztFBb.js} +1 -1
  58. package/static/app/assets/{button-COwyqfHM.js → button-CjueubVZ.js} +1 -1
  59. package/static/app/assets/circle-check-CLeWJr67.js +1 -0
  60. package/static/app/assets/{circle-pause-DEM4A1Y5.js → circle-pause-HRjSmpyY.js} +1 -1
  61. package/static/app/assets/{circle-play-C9djDuLd.js → circle-play-CMcKDVwu.js} +1 -1
  62. package/static/app/assets/{cpu-DFdo1gw-.js → cpu-B_MyTfYr.js} +1 -1
  63. package/static/app/assets/{download-SnJL6oqk.js → download-CTy0EByV.js} +1 -1
  64. package/static/app/assets/{folder-open-CqZeDkjE.js → folder-open-DF6bLhTg.js} +1 -1
  65. package/static/app/assets/{hard-drive-j1jJXYYf.js → hard-drive-CpWHG77C.js} +1 -1
  66. package/static/app/assets/{index-_u5iUHDr.js → index-Dd6abJHX.js} +3 -3
  67. package/static/app/assets/index-DxmOfNRi.css +2 -0
  68. package/static/app/assets/{input-B0lPdRQZ.js → input-bVgIRN3s.js} +1 -1
  69. package/static/app/assets/{link-2-CoFbooHS.js → link-2-bg6CG5Vk.js} +1 -1
  70. package/static/app/assets/{permissionCopy-BsyLxtao.js → permissionCopy-DFTf1HDZ.js} +1 -1
  71. package/static/app/assets/{primitives-DEbN-d6p.js → primitives-D7D-sQ3T.js} +1 -1
  72. package/static/app/assets/search-BgSVX6NG.js +1 -0
  73. package/static/app/assets/{share-2-CVtZ_ewX.js → share-2-BLzo7U4L.js} +1 -1
  74. package/static/app/assets/{shield-alert-CBi2GNWM.js → shield-alert-BmngqnsJ.js} +1 -1
  75. package/static/app/assets/{textarea-DNMpB5ih.js → textarea-DOSlAQQ3.js} +1 -1
  76. package/static/app/assets/{useFocusTrap-C83t3GXF.js → useFocusTrap-pZhgeee4.js} +1 -1
  77. package/static/app/assets/{useMutation-DtbJDoyz.js → useMutation-BMDwNk4I.js} +1 -1
  78. package/static/app/assets/{useQuery-Dcp1OChy.js → useQuery-CSjKttRo.js} +1 -1
  79. package/static/app/assets/{utils-BlZr7Pd4.js → utils-D3u_yv7B.js} +1 -1
  80. package/static/app/assets/{workspace-jJY4RuAV.js → workspace-H3bMjYXC.js} +1 -1
  81. package/static/app/index.html +4 -4
  82. package/static/sw.js +1 -1
  83. package/static/app/assets/arrow-left-DXvKg9U6.js +0 -1
  84. package/static/app/assets/circle-check-DfInj-qD.js +0 -1
  85. package/static/app/assets/index-BLPb5lmE.css +0 -2
  86. package/static/app/assets/search-BybIWPNd.js +0 -1
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env python3
2
+ """The document-generation half of the Python↔Rust parity corpus (v11.5.1).
3
+
4
+ ``scripts/generate_rust_parity_fixtures.py`` owns the store and the goldens;
5
+ this module owns the rows and specs the **document-generation** ports need, so
6
+ neither file has to grow past the tree's file-size ceiling. Nothing here runs
7
+ anything — it is data plus one normalization helper.
8
+
9
+ Three things are shaped on purpose:
10
+
11
+ * **Every allow-listed node type appears.** ``search_for_document_generation``
12
+ admits fifteen types and boosts four of them; a corpus that only carried
13
+ ``Document`` would prove the boost and the filter equally badly.
14
+ * **The Self-Model subgraph is real.** ``summary_for_prompt`` reads the legacy
15
+ ``nodes`` table for ``self:%`` ids, so the profile rows are written through
16
+ the same write door as everything else, including the root the read excludes
17
+ and a ``self:`` row whose metadata carries no recognised kind (dropped).
18
+ * **``updated_at`` is spread against the frozen clock**, including one stamp
19
+ that does not parse — the recency term's ``0.0`` branch is otherwise
20
+ unreachable from a fixture whose every row is well formed.
21
+
22
+ :func:`normalize_multi_hop` is the one deliberate deviation, and it is a
23
+ *golden* normalization rather than a port divergence: see its docstring.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any, Dict, List, Optional, Tuple
29
+
30
+ WS_ALPHA = "ws-alpha"
31
+ WS_BETA = "ws-beta"
32
+
33
+ # ── the document-generation corpus ───────────────────────────────────────────
34
+ # (node_id, type, title, summary, metadata, workspace_id, updated_at)
35
+ DOCGEN_NODES: List[Tuple[str, str, str, str, Dict[str, Any], Optional[str], str]] = [
36
+ # ── the Self-Model subgraph (read by `summary_for_prompt`) ──────────────
37
+ # `self:root` is excluded by id, and `self:unknown:*` carries no
38
+ # `self_model_kind`, so `_row_to_fact` drops it — both are read branches
39
+ # that only exist if the fixture actually contains them.
40
+ ("self:root", "Self", "나", "Brain이 사용자에 대해 알고 있는 사실의 뿌리입니다.",
41
+ {"self_model": True, "self_model_kind": "root"}, None, "2026-07-01T09:00:00"),
42
+ ("self:decision:a1", "Decision", "문서는 한국어로 쓴다",
43
+ "문서는 한국어로 쓴다",
44
+ {"self_model": True, "self_model_kind": "decision", "origin": "user",
45
+ "confidence": 0.9, "signal": "ko_decision_marker", "workspace_id": WS_ALPHA},
46
+ WS_ALPHA, "2026-07-02T09:00:00"),
47
+ ("self:habit:b2", "Habit", "매주 월요일 회의록을 정리한다",
48
+ "매주 월요일 회의록을 정리한다",
49
+ {"self_model": True, "self_model_kind": "habit", "origin": "extraction",
50
+ "confidence": 0.6, "signal": "ko_habit", "workspace_id": None},
51
+ None, "2026-07-03T09:00:00"),
52
+ ("self:preference:c3", "Preference", "설명은 짧고 근거를 먼저",
53
+ "설명은 짧고 근거를 먼저",
54
+ {"self_model": True, "self_model_kind": "preference", "origin": "user",
55
+ "confidence": 0.8, "signal": "ko_prefer", "workspace_id": None},
56
+ None, "2026-07-04T09:00:00"),
57
+ ("self:relationship:d4", "Relationship", "박민서 님과 빌드 파이프라인을 함께 본다",
58
+ "박민서 님과 빌드 파이프라인을 함께 본다",
59
+ {"self_model": True, "self_model_kind": "relationship", "origin": "extraction",
60
+ "confidence": 0.5, "signal": "ko_with", "workspace_id": WS_BETA},
61
+ WS_BETA, "2026-07-05T09:00:00"),
62
+ ("self:trait:e5", "Self", "검색 품질 담당자",
63
+ "검색 품질 담당자",
64
+ {"self_model": True, "self_model_kind": "trait", "origin": "user",
65
+ "confidence": 0.7, "signal": "ko_role", "workspace_id": None},
66
+ None, "2026-07-06T09:00:00"),
67
+ ("self:unknown:f6", "Concept", "kind가 없는 self 노드",
68
+ "self_model_kind가 없어 프로필에서 제외되는 행입니다.",
69
+ {"self_model": True}, None, "2026-07-07T09:00:00"),
70
+ # ── the proposal cluster: one hub document with more qualifying
71
+ # neighbours than the eight the related-concept join keeps ───────────
72
+ ("doc:proposal", "Document", "제안서 초안: 검색 품질 개선",
73
+ "검색 품질 개선 제안서 초안입니다. 근거는 주간 회의 기록과 분기 리뷰 발표자료입니다.",
74
+ {"relative_path": "docs/proposal.md", "filename": "proposal.md", "ext": ".md"},
75
+ WS_ALPHA, "2026-07-30T09:00:00"),
76
+ ("doc:release-notes", "Document", "Release notes for the retrieval port",
77
+ "Release notes describing the ranking port and the parity harness.",
78
+ {"source": "release"}, None, "2026-07-28T09:00:00"),
79
+ ("file:meeting-minutes", "File", "meeting-minutes.md",
80
+ "회의록: 랭킹 개선과 온보딩 체크리스트를 확정했습니다.",
81
+ {"filename": "meeting-minutes.md", "relative_path": "notes/meeting-minutes.md"},
82
+ WS_ALPHA, "2026-08-01T11:00:00"),
83
+ ("deck:proposal", "SlideDeck", "제안 발표자료",
84
+ "제안서 초안을 발표용으로 정리한 자료입니다.",
85
+ {"filename": "proposal.pptx", "ext": ".pptx"}, WS_BETA, "2026-07-29T09:00:00"),
86
+ ("chat:proposal", "Chat", "제안서 관련 대화",
87
+ "제안서 초안을 어떻게 쓸지 논의한 대화입니다.",
88
+ {"conversation_id": "conv-f"}, WS_ALPHA, "2026-07-26T09:00:00"),
89
+ ("task:proposal-review", "Task", "제안서 검토",
90
+ "제안서 초안을 검토하고 근거를 확인합니다.", {"status": "open"},
91
+ WS_ALPHA, "2026-07-27T09:00:00"),
92
+ ("dec:doc-format", "Decision", "문서 형식은 마크다운으로 통일",
93
+ "문서 생성 결과는 마크다운으로 통일하기로 했습니다.", {},
94
+ WS_ALPHA, "2026-07-22T09:00:00"),
95
+ ("feature:doc-generation", "Feature", "문서 생성",
96
+ "질문에서 문서를 만들어 주는 기능입니다.", {}, WS_ALPHA, "2026-07-25T09:00:00"),
97
+ ("concept:proposal", "Concept", "제안서",
98
+ "제안서는 결정을 요청하는 문서입니다.", {}, None, "2026-07-24T09:00:00"),
99
+ ("concept:evidence", "Concept", "근거",
100
+ "근거는 주장을 뒷받침하는 자료입니다.", {}, WS_ALPHA, "2026-07-23T09:00:00"),
101
+ ("page:proposal-outline", "Page", "제안서 목차",
102
+ "제안서 목차 페이지입니다.", {}, None, "2026-07-21T09:00:00"),
103
+ ("slide:proposal-1", "Slide", "제안 슬라이드 1",
104
+ "제안서 요약 슬라이드입니다.", {}, WS_BETA, "2026-07-19T09:00:00"),
105
+ ("sheet:proposal-budget", "Spreadsheet", "proposal-budget.xlsx",
106
+ "제안서 예산 표입니다.", {"filename": "proposal-budget.xlsx", "ext": ".xlsx"},
107
+ WS_ALPHA, "2026-07-17T09:00:00"),
108
+ ("img:proposal-sketch", "Image", "proposal-sketch.png",
109
+ "제안서 구조 스케치입니다.", {"filename": "proposal-sketch.png", "ext": ".png"},
110
+ WS_BETA, "2026-07-16T09:00:00"),
111
+ ("imgtext:proposal-ocr", "ImageText", "제안서 스케치 OCR",
112
+ "제안서 구조: 배경, 근거, 결론.",
113
+ {"filename": "proposal-sketch.png", "ocr": True}, WS_BETA, "2026-07-16T09:01:00"),
114
+ ("audio:proposal-call", "Audio", "proposal-call.m4a",
115
+ "제안서 관련 통화 녹음입니다.", {"filename": "proposal-call.m4a", "ext": ".m4a"},
116
+ WS_ALPHA, "2026-07-15T09:00:00"),
117
+ ("code:doc-builder", "CodeFile", "context_builder.py",
118
+ "문서 생성 컨텍스트를 조합하는 코드입니다. 제안서 생성도 여기를 지납니다.",
119
+ {"filename": "context_builder.py",
120
+ "relative_path": "latticeai/core/context_builder.py"},
121
+ WS_ALPHA, "2026-07-14T09:00:00"),
122
+ # The recency term's `0.0` branch: a stamp `datetime.fromisoformat` refuses.
123
+ ("doc:undated", "Document", "Undated 제안서 memo",
124
+ "An undated memo about 제안서; its stamp does not parse.", {},
125
+ None, "not-a-timestamp"),
126
+ ]
127
+
128
+ # (from, to, type, weight, created_at) — appended to the base edge set.
129
+ DOCGEN_EDGES: List[Tuple[str, str, str, float, str]] = [
130
+ ("doc:proposal", "concept:proposal", "mentions", 0.92, "2026-07-30T00:00:00"),
131
+ ("doc:proposal", "concept:evidence", "mentions", 0.91, "2026-07-30T00:01:00"),
132
+ ("doc:proposal", "feature:doc-generation", "relates_to", 0.90, "2026-07-30T00:02:00"),
133
+ ("doc:proposal", "dec:doc-format", "relates_to", 0.89, "2026-07-30T00:03:00"),
134
+ ("doc:proposal", "task:proposal-review", "relates_to", 0.88, "2026-07-30T00:04:00"),
135
+ ("doc:proposal", "concept:retrieval", "mentions", 0.87, "2026-07-30T00:05:00"),
136
+ ("doc:proposal", "concept:ranking", "mentions", 0.86, "2026-07-30T00:06:00"),
137
+ ("doc:proposal", "feature:command-palette", "mentions", 0.85, "2026-07-30T00:07:00"),
138
+ ("doc:proposal", "task:parity-harness", "relates_to", 0.84, "2026-07-30T00:08:00"),
139
+ ("doc:proposal", "page:proposal-outline", "contains", 0.83, "2026-07-30T00:09:00"),
140
+ ("deck:proposal", "doc:proposal", "relates_to", 0.82, "2026-07-29T00:00:00"),
141
+ ("deck:proposal", "slide:proposal-1", "contains", 0.81, "2026-07-29T00:01:00"),
142
+ ("file:meeting-minutes", "dec:doc-format", "relates_to", 0.80, "2026-07-28T00:00:00"),
143
+ ("file:meeting-minutes", "meeting:weekly", "relates_to", 0.79, "2026-07-28T00:01:00"),
144
+ ("chat:proposal", "doc:proposal", "mentions", 0.78, "2026-07-27T00:00:00"),
145
+ ("img:proposal-sketch", "imgtext:proposal-ocr", "relates_to", 0.77, "2026-07-26T00:00:00"),
146
+ ("imgtext:proposal-ocr", "concept:proposal", "mentions", 0.76, "2026-07-26T00:01:00"),
147
+ ("audio:proposal-call", "task:proposal-review", "mentions", 0.75, "2026-07-25T00:00:00"),
148
+ ("code:doc-builder", "feature:doc-generation", "relates_to", 0.74, "2026-07-24T00:00:00"),
149
+ ("sheet:proposal-budget", "doc:proposal", "relates_to", 0.73, "2026-07-23T00:00:00"),
150
+ ("doc:release-notes", "concept:ranking", "mentions", 0.72, "2026-07-22T00:00:00"),
151
+ ("doc:undated", "concept:proposal", "mentions", 0.71, "2026-07-21T00:00:00"),
152
+ # The Self-Model's own shape: every fact points at the root.
153
+ ("self:decision:a1", "self:root", "PART_OF", 1.0, "2026-07-02T00:00:00"),
154
+ ("self:habit:b2", "self:root", "PART_OF", 1.0, "2026-07-03T00:00:00"),
155
+ ("self:preference:c3", "self:root", "PART_OF", 1.0, "2026-07-04T00:00:00"),
156
+ ("self:relationship:d4", "self:root", "PART_OF", 1.0, "2026-07-05T00:00:00"),
157
+ ("self:trait:e5", "self:root", "PART_OF", 1.0, "2026-07-06T00:00:00"),
158
+ ("self:unknown:f6", "self:root", "PART_OF", 1.0, "2026-07-07T00:00:00"),
159
+ ]
160
+
161
+ # ── suite specs ──────────────────────────────────────────────────────────────
162
+ DOCGEN_SEARCH_SPECS: List[Dict[str, Any]] = [
163
+ {"key": "ko_proposal", "query": "제안서 초안"},
164
+ {"key": "ko_meeting", "query": "회의 결정 사항"},
165
+ {"key": "en_ranking", "query": "hybrid retrieval ranking"},
166
+ {"key": "ko_sketch", "query": "스케치"},
167
+ {"key": "ko_self_model", "query": "문서는 한국어로"},
168
+ {"key": "en_undated", "query": "Undated"},
169
+ {"key": "hub_deck", "query": "제안 발표자료"},
170
+ {"key": "tie", "query": "Tie candidate"},
171
+ {"key": "no_hit", "query": "zzqq wumpus nonsense"},
172
+ {"key": "empty_query", "query": ""},
173
+ {"key": "blank_query", "query": " "},
174
+ {"key": "limit_one", "query": "제안서", "limit": 1},
175
+ {"key": "limit_zero", "query": "제안서", "limit": 0},
176
+ {"key": "limit_over", "query": "제안서", "limit": 900},
177
+ {"key": "scoped_alpha", "query": "제안서", "allowed": [WS_ALPHA]},
178
+ {"key": "scoped_alpha_legacy", "query": "제안서", "allowed": [WS_ALPHA], "legacy": True},
179
+ {"key": "scoped_beta", "query": "제안서", "allowed": [WS_BETA]},
180
+ {"key": "scoped_empty", "query": "제안서", "allowed": []},
181
+ ]
182
+
183
+ MULTI_HOP_SPECS: List[Dict[str, Any]] = [
184
+ *[{"key": f"hub_h{hops}", "node_ids": ["doc:proposal"], "max_hops": hops}
185
+ for hops in (0, 1, 2, 3)],
186
+ {"key": "hub_hneg", "node_ids": ["doc:proposal"], "max_hops": -1},
187
+ {"key": "multi_seed", "node_ids": ["doc:proposal", "dec:fusion-alpha"], "max_hops": 2},
188
+ {"key": "leaf", "node_ids": ["slide:proposal-1"], "max_hops": 2},
189
+ {"key": "isolated", "node_ids": ["tie:c"], "max_hops": 2},
190
+ {"key": "self_root", "node_ids": ["self:root"], "max_hops": 2},
191
+ {"key": "missing_seed", "node_ids": ["nope:missing"], "max_hops": 2},
192
+ {"key": "no_seeds", "node_ids": [], "max_hops": 2},
193
+ {"key": "scoped_alpha", "node_ids": ["doc:proposal"], "max_hops": 2,
194
+ "allowed": [WS_ALPHA]},
195
+ {"key": "scoped_alpha_legacy", "node_ids": ["doc:proposal"], "max_hops": 2,
196
+ "allowed": [WS_ALPHA], "legacy": True},
197
+ {"key": "scoped_beta", "node_ids": ["deck:proposal"], "max_hops": 2,
198
+ "allowed": [WS_BETA]},
199
+ {"key": "scoped_empty", "node_ids": ["doc:proposal"], "max_hops": 2, "allowed": []},
200
+ ]
201
+
202
+ CONTEXT_DOCUMENT_SPECS: List[Dict[str, Any]] = [
203
+ {"key": "default", "query": "제안서 초안"},
204
+ {"key": "no_self_model", "query": "제안서 초안", "include_self_model": False},
205
+ {"key": "self_model_tokens_small", "query": "제안서 초안", "self_model_tokens": 12},
206
+ {"key": "budget_240", "query": "제안서 초안", "budget": 240},
207
+ {"key": "budget_120", "query": "제안서 초안", "budget": 120},
208
+ {"key": "budget_40", "query": "제안서 초안", "budget": 40},
209
+ {"key": "budget_one", "query": "제안서 초안", "budget": 1},
210
+ {"key": "budget_zero", "query": "제안서 초안", "budget": 0},
211
+ {"key": "hops_zero", "query": "제안서 초안", "max_hops": 0},
212
+ {"key": "hops_three", "query": "제안서 초안", "max_hops": 3},
213
+ {"key": "results_two", "query": "제안서 초안", "max_results": 2},
214
+ {"key": "ko_meeting", "query": "회의 결정 사항"},
215
+ {"key": "en_ranking", "query": "hybrid retrieval ranking"},
216
+ {"key": "ko_sketch", "query": "스케치"},
217
+ {"key": "fallback_lexical", "query": "박민서 님"},
218
+ {"key": "fallback_empty", "query": "zzqq wumpus nonsense"},
219
+ {"key": "empty_query", "query": ""},
220
+ {"key": "scoped_alpha", "query": "제안서 초안", "allowed": [WS_ALPHA]},
221
+ {"key": "scoped_alpha_legacy", "query": "제안서 초안", "allowed": [WS_ALPHA],
222
+ "legacy": True},
223
+ {"key": "scoped_empty", "query": "제안서 초안", "allowed": []},
224
+ ]
225
+
226
+
227
+ def normalize_multi_hop(payload: Dict[str, Any]) -> Dict[str, Any]:
228
+ """Order a ``multi_hop_context`` answer deterministically.
229
+
230
+ **This is a golden normalization, not a port divergence.** Python's
231
+ traversal iterates ``frontier``, a ``set`` — so the order in which the
232
+ nodes of one hop are appended (and therefore the order their incident
233
+ edges are appended) depends on string hashing, which CPython randomizes
234
+ per process unless ``PYTHONHASHSEED`` is pinned. The *contents* are fully
235
+ determined (which nodes are visited, which edges are collected, and every
236
+ node's hop label); only the sequence is not, so the live response ordering
237
+ is unspecified and there is nothing for a port to reproduce.
238
+
239
+ Both sides therefore sort before comparing: nodes by ``(hop, id)``, edges
240
+ by ``(from, to, type, weight)``. The Rust port emits that order natively —
241
+ it is deterministic where Python is not, which is a strictly stronger
242
+ promise and not a different answer.
243
+ """
244
+ return {
245
+ "nodes": sorted(payload["nodes"], key=lambda node: (node["hop"], node["id"])),
246
+ "edges": sorted(
247
+ payload["edges"],
248
+ key=lambda edge: (edge["from"], edge["to"], edge["type"], edge["weight"]),
249
+ ),
250
+ }
251
+
252
+
253
+ class OrderedMultiHopStore:
254
+ """The store, with ``multi_hop_context`` ordered deterministically.
255
+
256
+ ``retrieve_context_for_generation`` feeds the traversal's node list into
257
+ ``extra_nodes``, and the concept section renders the first eight of it — so
258
+ Python's unordered ``frontier`` iteration reaches the *rendered markdown*,
259
+ not only the traversal payload. Both sides therefore see the normalized
260
+ order (see :func:`normalize_multi_hop`); every other attribute is forwarded
261
+ to the real store untouched.
262
+ """
263
+
264
+ def __init__(self, store: Any):
265
+ self._store = store
266
+
267
+ def __getattr__(self, name: str) -> Any:
268
+ return getattr(self._store, name)
269
+
270
+ def multi_hop_context(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
271
+ return normalize_multi_hop(self._store.multi_hop_context(*args, **kwargs))
272
+
273
+
274
+ def _scope(spec: Dict[str, Any]) -> Dict[str, Any]:
275
+ """The graph-layer scope every document-generation read takes."""
276
+ allowed = spec.get("allowed")
277
+ return {
278
+ "allowed_workspaces": None if allowed is None else set(allowed),
279
+ "include_legacy_global": spec.get("legacy", False),
280
+ }
281
+
282
+
283
+ def run_docgen_search(harness: Any, spec: Dict[str, Any]) -> List[Dict[str, Any]]:
284
+ """``KnowledgeGraphStore.search_for_document_generation`` for one spec."""
285
+ return harness.store.search_for_document_generation(
286
+ spec["query"], limit=spec.get("limit", 10), **_scope(spec)
287
+ )
288
+
289
+
290
+ def run_multi_hop(harness: Any, spec: Dict[str, Any]) -> Dict[str, Any]:
291
+ """``multi_hop_context`` for one spec, in the normalized order."""
292
+ return normalize_multi_hop(
293
+ harness.store.multi_hop_context(
294
+ spec["node_ids"], max_hops=spec.get("max_hops", 2), **_scope(spec)
295
+ )
296
+ )
297
+
298
+
299
+ def run_context_document(harness: Any, spec: Dict[str, Any]) -> Dict[str, Any]:
300
+ """``retrieve_context_for_generation`` plus the footnote over its sources.
301
+
302
+ The footnote is a second public entry point over the same ``sources`` list;
303
+ carrying it here proves that port too, without a suite of its own.
304
+ """
305
+ from lattice_brain.self_model import DEFAULT_SUMMARY_TOKENS
306
+ from latticeai.core.context_builder import (
307
+ DEFAULT_DOCUMENT_CONTEXT_BUDGET,
308
+ format_sources_footnote,
309
+ retrieve_context_for_generation,
310
+ )
311
+
312
+ context = retrieve_context_for_generation(
313
+ OrderedMultiHopStore(harness.store),
314
+ spec["query"],
315
+ max_results=spec.get("max_results", 10),
316
+ max_hops=spec.get("max_hops", 2),
317
+ budget=spec.get("budget", DEFAULT_DOCUMENT_CONTEXT_BUDGET),
318
+ include_self_model=spec.get("include_self_model", True),
319
+ self_model_tokens=spec.get("self_model_tokens", DEFAULT_SUMMARY_TOKENS),
320
+ **_scope(spec),
321
+ )
322
+ return {
323
+ "context": context,
324
+ "sources_footnote": format_sources_footnote(context["sources"]),
325
+ }
326
+
327
+
328
+ #: The three document-generation suites, as the generator merges them into
329
+ #: :data:`SUITES` / :data:`SUITE_RUNNERS`. Keeping the pair side by side is what
330
+ #: stops a spec list from arriving without the runner that answers it.
331
+ DOCGEN_SUITES: Dict[str, List[Dict[str, Any]]] = {
332
+ "docgen_search": DOCGEN_SEARCH_SPECS,
333
+ "multi_hop": MULTI_HOP_SPECS,
334
+ "context_document": CONTEXT_DOCUMENT_SPECS,
335
+ }
336
+
337
+ DOCGEN_RUNNERS: Dict[str, Any] = {
338
+ "docgen_search": run_docgen_search,
339
+ "multi_hop": run_multi_hop,
340
+ "context_document": run_context_document,
341
+ }
@@ -21,6 +21,27 @@
21
21
  "specific, not less."
22
22
  ],
23
23
 
24
+ "11.5.1": {
25
+ "_why": [
26
+ "Rust Full Loop release: agent-loop orchestrator port, worker seams,",
27
+ "doc-gen context builder — backend only. The frontend bundle is",
28
+ "untouched; no captured screen changes on purpose. All thirteen",
29
+ "measured but not gated. Known jitter: 05-memory-graph moves ~2-3%",
30
+ "between captures from the cytoscape layout (11.3.0-11.5.0 observed)."
31
+ ]
32
+ },
33
+
34
+ "11.5.0": {
35
+ "_why": [
36
+ "Rust Complete release: backend/desktop-topology architecture only",
37
+ "(router mounts, front-door default, scheduler, safety kernel). The",
38
+ "frontend bundle is untouched; no captured screen changes on purpose.",
39
+ "All thirteen measured but not gated. Known jitter: 05-memory-graph",
40
+ "moves ~2-3% between captures from the cytoscape layout (observed at",
41
+ "11.3.0 and 11.4.0 with zero source change)."
42
+ ]
43
+ },
44
+
24
45
  "11.4.0": {
25
46
  "_why": [
26
47
  "Rust Foundation release: backend architecture only (rust/ workspace,",
@@ -1728,22 +1728,40 @@ dependencies = [
1728
1728
  "unicode-segmentation",
1729
1729
  ]
1730
1730
 
1731
+ [[package]]
1732
+ name = "lattice-agent"
1733
+ version = "11.5.1"
1734
+ dependencies = [
1735
+ "axum",
1736
+ "base64 0.22.1",
1737
+ "fancy-regex",
1738
+ "futures-core",
1739
+ "getrandom 0.4.3",
1740
+ "reqwest 0.12.28",
1741
+ "serde",
1742
+ "serde_json",
1743
+ "sha2",
1744
+ "tokio",
1745
+ ]
1746
+
1731
1747
  [[package]]
1732
1748
  name = "lattice-ai-desktop"
1733
- version = "11.4.0"
1749
+ version = "11.5.1"
1734
1750
  dependencies = [
1735
1751
  "lattice-host",
1752
+ "lattice-jobs",
1736
1753
  "plist",
1737
1754
  "serde",
1738
1755
  "serde_json",
1739
1756
  "tauri",
1740
1757
  "tauri-build",
1741
1758
  "time",
1759
+ "tokio",
1742
1760
  ]
1743
1761
 
1744
1762
  [[package]]
1745
1763
  name = "lattice-core"
1746
- version = "11.4.0"
1764
+ version = "11.5.1"
1747
1765
  dependencies = [
1748
1766
  "blake2",
1749
1767
  "rusqlite",
@@ -1753,10 +1771,13 @@ dependencies = [
1753
1771
 
1754
1772
  [[package]]
1755
1773
  name = "lattice-host"
1756
- version = "11.4.0"
1774
+ version = "11.5.1"
1757
1775
  dependencies = [
1758
1776
  "axum",
1777
+ "lattice-agent",
1759
1778
  "lattice-core",
1779
+ "lattice-ingest",
1780
+ "lattice-jobs",
1760
1781
  "lattice-retrieval",
1761
1782
  "libc",
1762
1783
  "reqwest 0.12.28",
@@ -1765,15 +1786,43 @@ dependencies = [
1765
1786
  "tokio",
1766
1787
  ]
1767
1788
 
1789
+ [[package]]
1790
+ name = "lattice-ingest"
1791
+ version = "11.5.1"
1792
+ dependencies = [
1793
+ "axum",
1794
+ "lattice-core",
1795
+ "reqwest 0.12.28",
1796
+ "serde",
1797
+ "serde_json",
1798
+ "sha2",
1799
+ "tokio",
1800
+ ]
1801
+
1802
+ [[package]]
1803
+ name = "lattice-jobs"
1804
+ version = "11.5.1"
1805
+ dependencies = [
1806
+ "axum",
1807
+ "lattice-core",
1808
+ "reqwest 0.12.28",
1809
+ "serde",
1810
+ "serde_json",
1811
+ "tokio",
1812
+ ]
1813
+
1768
1814
  [[package]]
1769
1815
  name = "lattice-retrieval"
1770
- version = "11.4.0"
1816
+ version = "11.5.1"
1771
1817
  dependencies = [
1818
+ "axum",
1772
1819
  "fancy-regex",
1773
1820
  "lattice-core",
1821
+ "libc",
1774
1822
  "rusqlite",
1775
1823
  "serde",
1776
1824
  "serde_json",
1825
+ "tokio",
1777
1826
  ]
1778
1827
 
1779
1828
  [[package]]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "11.4.0"
3
+ version = "11.5.1"
4
4
  description = "Lattice AI Digital Brain desktop shell"
5
5
  authors = ["TaeSoo Park"]
6
6
  edition = "2021"
@@ -15,7 +15,14 @@ time = "=0.3.36"
15
15
  serde = { version = "1", features = ["derive"] }
16
16
  serde_json = "1"
17
17
  # The supervisor (command resolution, spawn, HTTP health gate, backoff restart,
18
- # graceful stop) shared with the `lattice-host` binary. A path dependency, not a
19
- # workspace member: `rust/` must stay buildable on a CI runner with no tauri
20
- # system libraries, so this crate deliberately sits outside that workspace.
18
+ # graceful stop) and, since 11.5.0, the gateway this shell serves as its front
19
+ # door. A path dependency, not a workspace member: `rust/` must stay buildable
20
+ # on a CI runner with no tauri system libraries, so this crate deliberately sits
21
+ # outside that workspace.
21
22
  lattice-host = { path = "../rust/lattice-host" }
23
+ # The jobs scheduler type the shell holds while the gateway exposes it on
24
+ # `/host/jobs`. Same reason for the path form.
25
+ lattice-jobs = { path = "../rust/lattice-jobs" }
26
+ # The gateway is an axum server and the shell drives it on tokio's runtime,
27
+ # which tauri already embeds.
28
+ tokio = { version = "1", features = ["sync", "time", "rt"] }