ltcai 9.8.0 → 9.9.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 (97) hide show
  1. package/README.md +45 -28
  2. package/bin/ltcai.js +1 -1
  3. package/desktop/electron/main.cjs +1 -1
  4. package/docs/BENCHMARKS.md +107 -0
  5. package/docs/CHANGELOG.md +82 -0
  6. package/docs/CI_AND_RELEASE_GATES.md +106 -0
  7. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  8. package/docs/DEVELOPMENT.md +11 -8
  9. package/docs/LEGACY_COMPATIBILITY.md +48 -49
  10. package/docs/ONBOARDING.md +1 -1
  11. package/docs/OPERATIONS.md +15 -6
  12. package/docs/PERFORMANCE.md +9 -1
  13. package/docs/SECURITY_AUDIT.md +91 -0
  14. package/docs/TRUST_MODEL.md +1 -1
  15. package/docs/USABILITY_AUDIT.md +164 -0
  16. package/docs/WHY_LATTICE.md +1 -1
  17. package/docs/architecture.md +4 -2
  18. package/docs/kg-schema.md +6 -6
  19. package/docs/spec-vs-impl.md +8 -1
  20. package/lattice_brain/__init__.py +1 -1
  21. package/lattice_brain/graph/schema.py +1 -1
  22. package/lattice_brain/runtime/multi_agent.py +1 -1
  23. package/latticeai/__init__.py +1 -1
  24. package/latticeai/api/review_queue.py +7 -0
  25. package/latticeai/core/agent.py +105 -8
  26. package/latticeai/core/agent_eval.py +99 -1
  27. package/latticeai/core/legacy_compatibility.py +71 -47
  28. package/latticeai/core/marketplace.py +1 -1
  29. package/latticeai/core/tool_governor.py +92 -1
  30. package/latticeai/core/workspace_os.py +1 -1
  31. package/latticeai/services/architecture_readiness.py +1 -1
  32. package/latticeai/services/change_proposals.py +165 -23
  33. package/latticeai/services/product_readiness.py +2 -2
  34. package/latticeai/services/tool_dispatch.py +33 -0
  35. package/latticeai/tools/__init__.py +1 -1
  36. package/latticeai/tools/knowledge.py +1 -1
  37. package/package.json +9 -17
  38. package/scripts/bench_models.py +312 -0
  39. package/scripts/check_bundle_budget.mjs +103 -0
  40. package/scripts/check_current_release_docs.mjs +20 -1
  41. package/scripts/check_doc_status.mjs +149 -0
  42. package/scripts/check_legacy_debt.mjs +91 -0
  43. package/scripts/generate_sbom.py +70 -0
  44. package/scripts/prune_release_evidence.mjs +38 -0
  45. package/scripts/wheel_smoke.py +22 -6
  46. package/src-tauri/Cargo.lock +1 -1
  47. package/src-tauri/Cargo.toml +1 -1
  48. package/src-tauri/src/main.rs +12 -12
  49. package/src-tauri/tauri.conf.json +1 -13
  50. package/static/app/asset-manifest.json +27 -11
  51. package/static/app/assets/Act-CcSWTQsx.js +1 -0
  52. package/static/app/assets/{Brain-BMkgdWnI.js → Brain-CSCDZEHd.js} +2 -2
  53. package/static/app/assets/BrainHome-B5QpJ1Fx.js +2 -0
  54. package/static/app/assets/BrainSignals-CmFD8PWk.js +1 -0
  55. package/static/app/assets/Capture-BiuerOio.js +1 -0
  56. package/static/app/assets/CommandPalette-BrqpT7K2.js +1 -0
  57. package/static/app/assets/Library-BcaiPbum.js +1 -0
  58. package/static/app/assets/LivingBrain-6FvA0H5T.js +1 -0
  59. package/static/app/assets/ProductFlow-Cr8bkQPw.js +1 -0
  60. package/static/app/assets/ReviewCard-D56TMypk.js +1 -0
  61. package/static/app/assets/System-D6uJacqu.js +1 -0
  62. package/static/app/assets/bot-DgC7xgUl.js +1 -0
  63. package/static/app/assets/cpu-DDGb4XHl.js +1 -0
  64. package/static/app/assets/download-C-Ei3GFa.js +1 -0
  65. package/static/app/assets/folder-open-BbVuSO9M.js +1 -0
  66. package/static/app/assets/hard-drive-yDZR6XYz.js +1 -0
  67. package/static/app/assets/index-CFRhNBuH.css +2 -0
  68. package/static/app/assets/index-DrZSkwrE.js +10 -0
  69. package/static/app/assets/input-DPIkTs9n.js +1 -0
  70. package/static/app/assets/navigation-B1mkmyUe.js +1 -0
  71. package/static/app/assets/network-0XDI1Uf_.js +1 -0
  72. package/static/app/assets/primitives-sIQYKyf5.js +1 -0
  73. package/static/app/assets/sunrise-DXMjueec.js +1 -0
  74. package/static/app/assets/textarea-DvnwpnqS.js +1 -0
  75. package/static/app/assets/utils-BrfFHAJl.js +7 -0
  76. package/static/app/index.html +4 -2
  77. package/static/sw.js +1 -1
  78. package/auto_setup.py +0 -21
  79. package/kg_schema.py +0 -13
  80. package/knowledge_graph.py +0 -51
  81. package/knowledge_graph_api.py +0 -24
  82. package/llm_router.py +0 -20
  83. package/local_knowledge_api.py +0 -16
  84. package/ltcai_cli.py +0 -21
  85. package/mcp_registry.py +0 -20
  86. package/p_reinforce.py +0 -16
  87. package/setup_wizard.py +0 -16
  88. package/static/app/assets/Act-Dd3z8AzF.js +0 -2
  89. package/static/app/assets/Capture-D2Aw9gkv.js +0 -1
  90. package/static/app/assets/Library-Yreq-KW5.js +0 -1
  91. package/static/app/assets/System-CXNmmtEo.js +0 -1
  92. package/static/app/assets/index-7gY9t9Sd.css +0 -2
  93. package/static/app/assets/index-CndfILiF.js +0 -18
  94. package/static/app/assets/primitives-DxsIXb6G.js +0 -1
  95. package/static/app/assets/textarea-DH7ne8VI.js +0 -1
  96. package/telegram_bot.py +0 -16
  97. package/tools/__init__.py +0 -29
@@ -1,13 +1,18 @@
1
- # Lattice AI — Operations Guide (v9.6.0)
1
+ # Lattice AI — Operations Guide (v9.9.1)
2
+
3
+ > **Status: canonical** — kept in sync with the current release. Storage layout
4
+ > below reflects the SQLite live Brain store and workspace scoping, not the
5
+ > earlier Markdown-per-node graph.
2
6
 
3
7
  ## 1. 데이터 파일 위치
4
8
 
5
9
  | 파일 | 용도 |
6
10
  |------|------|
7
11
  | `~/.ltcai/users.json` | 사용자 계정 (scrypt 해시 저장) |
8
- | `~/.ltcai/history.json` | 대화 히스토리 |
12
+ | `~/.ltcai/knowledge_graph.sqlite` | Brain 라이브 스토어 — Knowledge Graph 노드/엣지/청크, 대화 히스토리, 프로비넌스 (workspace_id 스코프) |
13
+ | `~/.ltcai/knowledge_graph_blobs/` | 업로드 원본/blob 저장소 |
9
14
  | `~/.ltcai/audit.json` | 감사 로그 (agent 실행, 사용자 변경 등) |
10
- | `~/.ltcai/brain/` | Knowledge Graph 노드 (Markdown) |
15
+ | `~/.ltcai/chat_history.json` | 레거시 대화 히스토리 최초 1회 SQLite로 idempotent 임포트 후 미사용 |
11
16
  | `~/ltcai-agent/` | Agent workspace (agent가 생성한 파일) |
12
17
 
13
18
  ## 2. 백업 및 복구
@@ -112,8 +117,10 @@ pip install --upgrade ltcai
112
117
 
113
118
  ### 6.3 데이터 마이그레이션 주의사항
114
119
  - `users.json` 스키마 변경 시: 서버가 자동으로 누락 필드를 기본값으로 보완합니다.
115
- - `history.json` 스키마 변경 시: 하위 호환성 유지. 구버전 레코드는 무시됩니다.
116
- - `brain/` Knowledge Graph: Markdown 파일 기반이므로 별도 마이그레이션 없음.
120
+ - 대화 히스토리: SQLite 라이브 스토어에 저장됩니다. 레거시 `chat_history.json`은
121
+ 최초 실행 1회 idempotent 임포트된 사용되지 않습니다.
122
+ - `knowledge_graph.sqlite` Knowledge Graph: SQLite 스키마 마이그레이션은 부팅 시
123
+ 적용되며 읽기 호환성/롤백/동등성 테스트로 보호됩니다.
117
124
 
118
125
  ## 7. 공개 서버 체크리스트
119
126
 
@@ -135,7 +142,9 @@ pip install --upgrade ltcai
135
142
  - 대화 히스토리는 `user_email` 필드로 필터링됩니다.
136
143
  - Agent workspace (`~/ltcai-agent/`)는 현재 단일 공유 디렉토리입니다.
137
144
  멀티 유저 환경에서는 `~/ltcai-agent/<email>/` 구조로 분리를 권장합니다.
138
- - Knowledge Graph (`brain/`) 역시 단일 공유 공간입니다.
145
+ - Knowledge Graph `workspace_id`로 스코프됩니다. 없는 스코프의 노드는
146
+ 기본 비공개(fail-closed)이며, 레거시 글로벌 읽기는 명시적 호환성 옵트인이
147
+ 있을 때만 허용됩니다.
139
148
 
140
149
  ## 9. 서버 시작 / 종료
141
150
 
@@ -1,5 +1,13 @@
1
1
  # Knowledge Graph Performance Baseline
2
2
 
3
+ > **Status: reference** — a synthetic regression baseline, not a
4
+ > current-release measurement. The numbers below were last captured on the
5
+ > v9.6.0 working tree (see provenance line) and are carried forward as the
6
+ > comparison reference. The 9.7.0-9.8.0 line added graph-layer paths
7
+ > (`hybrid_search`, vector freshness, extraction-quality scoring), so
8
+ > regenerate with `scripts/profile_kg.py` on the current tree before treating
9
+ > these as 9.8.0 numbers.
10
+
3
11
  Synthetic performance and memory baseline for `KnowledgeGraphStore`
4
12
  (`lattice_brain/graph/`). Measured with `scripts/profile_kg.py`.
5
13
 
@@ -34,7 +42,7 @@ Run it:
34
42
  .venv/bin/python scripts/profile_kg.py --json # machine-readable
35
43
  ```
36
44
 
37
- ## Measured baseline (2026-07-20, v9.6.0 working tree)
45
+ ## Measured baseline (last captured on the v9.6.0 working tree)
38
46
 
39
47
  Apple Silicon (darwin), Python 3.x from `.venv`, SQLite with FTS5 trigram
40
48
  available, tracemalloc enabled.
@@ -0,0 +1,91 @@
1
+ # Security Audit — Static Scan Snapshot
2
+
3
+ > Status: audit snapshot 2026-07-21
4
+
5
+ This is a point-in-time static-analysis snapshot for release **v9.8.0**. It
6
+ records exactly which scanners ran, the raw findings, and an honest per-finding
7
+ real-risk assessment. It is not a guarantee of absence of vulnerabilities; it is
8
+ evidence of what the listed tools saw on the listed date.
9
+
10
+ Related: [CI & Release Gates](CI_AND_RELEASE_GATES.md) documents how these scans
11
+ are wired into continuous integration (`.github/workflows/dependency-audit.yml`).
12
+
13
+ ## Tooling & commands
14
+
15
+ Scanners were installed into the project virtualenv only (`.venv`), never added
16
+ to runtime dependencies:
17
+
18
+ ```
19
+ .venv/bin/python -m pip install pip-audit bandit
20
+ ```
21
+
22
+ | Tool | Version | Command |
23
+ |-----------|---------|---------|
24
+ | pip-audit | 2.10.1 | `.venv/bin/python -m pip_audit -r requirements.txt --strict` |
25
+ | pip-audit | 2.10.1 | `.venv/bin/python -m pip_audit` (installed environment) |
26
+ | bandit | 1.9.4 | `.venv/bin/python -m bandit -r latticeai lattice_brain -ll` |
27
+ | npm audit | (npm 10)| `npm audit --audit-level=high` |
28
+
29
+ ## Dependency vulnerabilities (pip-audit / npm audit)
30
+
31
+ **Result: no known vulnerabilities.**
32
+
33
+ - `pip-audit -r requirements.txt --strict` → `No known vulnerabilities found`.
34
+ - `pip-audit` over the installed environment → `No known vulnerabilities found`.
35
+ - `npm audit --audit-level=high` → `found 0 vulnerabilities`.
36
+
37
+ No dependency version changes were required. Because there were no actual
38
+ vulnerable pins to fix, no dependency files were modified (per the honest-fix
39
+ rule: only fix a real, safe-to-fix issue).
40
+
41
+ ## Static code analysis (bandit)
42
+
43
+ Scanned **54,795 lines** across `latticeai/` and `lattice_brain/`. With the
44
+ `-ll` display filter (MEDIUM severity and above), bandit reported **80 findings**:
45
+ 1 HIGH and 79 MEDIUM. Breakdown by rule:
46
+
47
+ | Count | Severity | Rule | What it flags |
48
+ |------:|----------|------|---------------|
49
+ | 1 | HIGH | B324 hashlib | Weak MD5 hash "for security" |
50
+ | 58 | MEDIUM | B608 hardcoded_sql_expressions | SQL built via string formatting |
51
+ | 7 | MEDIUM | B310 blacklist (urlopen) | `urllib.request.urlopen` with unrestricted scheme |
52
+ | 5 | MEDIUM | B604 shell=True | A function called with `shell=True` |
53
+ | 3 | MEDIUM | B108 hardcoded_tmp_directory | Hardcoded `/tmp` path |
54
+ | 3 | MEDIUM | B104 hardcoded_bind_all_interfaces | Bind to `0.0.0.0` |
55
+ | 2 | MEDIUM | B306 blacklist (mktemp) | `tempfile.mktemp()` |
56
+ | 1 | MEDIUM | B615 huggingface_unsafe_download | `hf_hub_download` without a pinned revision |
57
+
58
+ ### Per-finding real-risk assessment
59
+
60
+ | Rule | Location(s) | Verdict | Rationale |
61
+ |------|-------------|---------|-----------|
62
+ | **B324** MD5 | `lattice_brain/quality.py:44` | **False positive** | MD5 derives a deterministic *pseudo-label* for embedding clusters (`emb_cluster_<hash>`); not used for authentication, integrity, or secrets. Safe cosmetic fix would be `hashlib.md5(..., usedforsecurity=False)`; source is out of this task's edit scope, so documented only. |
63
+ | **B604** shell=True | `latticeai/core/agent.py:539`, `latticeai/core/tool_registry.py:129/136/168/175` | **False positive** | These are `ToolPolicy(..., shell=True, ...)` dataclass *declarations* describing whether a tool is a shell tool — not `subprocess(..., shell=True)` calls. Bandit matches the keyword, not an actual shell invocation. |
64
+ | **B108** /tmp | `lattice_brain/graph/_kg_constants.py:186/215`, `latticeai/core/agent_eval.py:240` | **False positive / benign** | The `_kg_constants` hits are `/tmp` entries in an **exclusion allowlist** of directories to skip during ingestion (a defensive list). The `agent_eval` hit is a test/eval default `agent_root`. No untrusted file is written to a predictable temp path here. |
65
+ | **B104** 0.0.0.0 | `latticeai/cli/entrypoint.py:53/237/238` | **By design, low risk** | The bind switches to `0.0.0.0` only when the operator explicitly passes `--tunnel`; default remains `127.0.0.1`. Intentional and gated. |
66
+ | **B310** urlopen | `latticeai/services/model_engines.py:120/234`, `latticeai/services/model_runtime.py:242` | **Low real risk** | Calls target operator-configured local inference engines (LM Studio / Ollama). URLs are not attacker-controlled in normal use. Recommendation: assert `http(s)` scheme before `urlopen` to close the `file:`/custom-scheme door. |
67
+ | **B608** SQL f-strings | `lattice_brain/storage/postgres.py`, `.../migration.py`, `lattice_brain/graph/*.py`, `lattice_brain/conversations.py` | **Low real risk — review** | Interpolated fragments are overwhelmingly **schema/table identifiers** quoted through `lattice_brain.storage.postgres._quote_ident`, with row *values* passed as bound parameters. Not user-value injection. Standing recommendation: keep every interpolated identifier sourced from internal constants / allowlists, never from request input. |
68
+ | **B306** mktemp | `latticeai/integrations/telegram_bot.py:450/568` | **Real, low severity** | `tempfile.mktemp()` has a documented create-time race; prefer `tempfile.mkstemp()` / `NamedTemporaryFile`. Impact is limited (local temp files for outbound Telegram media), but this is a genuine hardening item for a future source-owning change. |
69
+ | **B615** HF download | `latticeai/services/model_runtime.py:601` | **Real, supply-chain relevant** | `hf_hub_download(...)` is called without `revision=`, so a mutated upstream model repo could serve a different artifact than expected. Recommendation: pin a commit/`revision` (or verify a hash) when fetching model weights. Ties directly into the P2 supply-chain theme. |
70
+
71
+ ## Actions taken vs deferred
72
+
73
+ - **Fixed:** nothing — no dependency vulnerability existed to fix safely, and
74
+ the code-level findings live in source files outside this task's edit scope.
75
+ - **Deferred (documented) hardening items**, for a future change that owns the
76
+ relevant source:
77
+ 1. Pin a `revision` in `model_runtime.py` `hf_hub_download` (B615).
78
+ 2. Replace `tempfile.mktemp()` in `integrations/telegram_bot.py` (B306).
79
+ 3. Validate URL scheme before `urlopen` in the model engine paths (B310).
80
+ 4. Optional cosmetic: `usedforsecurity=False` on the clustering MD5 (B324).
81
+
82
+ ## Limitations (honest)
83
+
84
+ - Static analysis only. No dynamic/DAST, no fuzzing, no secret-scanning here.
85
+ - bandit reasons syntactically; the false positives above were confirmed by
86
+ reading each site, not by suppression.
87
+ - pip-audit resolves against the OSV/PyPI advisory database as of the snapshot
88
+ date; new advisories appear continuously — see the scheduled
89
+ `dependency-audit` workflow for the ongoing signal.
90
+ - SBOMs (CycloneDX) for both ecosystems can be regenerated with
91
+ `.venv/bin/python scripts/generate_sbom.py`.
@@ -1,6 +1,6 @@
1
1
  # Lattice AI Trust Model
2
2
 
3
- Current release: **9.8.0Honest Knowledge Pipeline**.
3
+ Current release: **9.9.1Clean Foundations**.
4
4
 
5
5
  Lattice AI is local-first, explicit about external communication, and honest
6
6
  when a capability is unavailable.
@@ -0,0 +1,164 @@
1
+ # Usability Heuristic Audit
2
+
3
+ > Status: audit snapshot 2026-07-21
4
+
5
+ A heuristic (expert) evaluation of the Lattice AI product surface against
6
+ Jakob Nielsen's 10 usability heuristics, focused on the five journeys called out
7
+ in external review: **device analysis**, **agent completion**, **change
8
+ approval**, **Brain hierarchy**, and **slow first run**.
9
+
10
+ **This is not a substitute for real-user testing.** Heuristic evaluation finds
11
+ plausible problems from an expert's read of the code and UI; it cannot measure
12
+ what real users actually do, misread, or abandon. Every recommendation below
13
+ should be validated with 5–8 real sessions before it is treated as fact.
14
+
15
+ Evidence is cited as `file:line` against the frontend source
16
+ (`frontend/src/**`, TypeScript/React) and Python services. Note the shipped
17
+ `static/app/assets/*.js` is a minified bundle and is not citable line-by-line;
18
+ citations point at the readable source it is built from.
19
+
20
+ ## Journey 1 — Device analysis (onboarding)
21
+
22
+ Surface: `frontend/src/components/onboarding/AnalysisScreen.tsx`; backing logic
23
+ `latticeai/services/model_recommendation.py`.
24
+
25
+ - The screen renders **real detected facts**, not pure theater: it maps over a
26
+ `analysis` prop and shows real RAM when available
27
+ (`AnalysisScreen.tsx:18`, `:95` — `ramGb ? \`${Math.round(ramGb)} GB\``).
28
+ - RAM sizing and Apple-Silicon detection are honest, documented estimates:
29
+ `estimated_ram_gb = size_gb * 1.25 + 2.5` (`model_recommendation.py:64`),
30
+ `is_apple_silicon` (`:69`), `recommend_catalog` (`:160`).
31
+
32
+ Heuristics:
33
+ - **#1 Visibility of system status** — GOOD: hardware line items begin in a
34
+ `flow.analysis.checking` state (`AnalysisScreen.tsx:63-67`) then resolve to
35
+ detected values, so the scan reads as progress rather than a frozen screen.
36
+ - **#2 Match to the real world** — GOOD: plain-language labels (chip, RAM, GPU,
37
+ support, models) via i18n keys rather than jargon.
38
+ - **#1 risk** — the staged "checking → detected" reveal is presentation timing,
39
+ not the true probe duration; if detection is effectively instant, the delay is
40
+ cosmetic. Recommend: keep the animation only as long as real work is pending,
41
+ and label any assumed/estimated value (e.g. RAM heuristic) as an estimate so
42
+ users don't read `~16 GB` as a precise measurement.
43
+
44
+ ## Journey 2 — Agent completion
45
+
46
+ Surface: `latticeai/core/agent.py`.
47
+
48
+ - Terminal states are explicit: `DONE`, `FAILED`, `NEEDS_REVIEW`
49
+ (`agent.py:50-56`), and a user-facing `final_message` is always set
50
+ (`:307`, and the default `"작업을 완료했습니다."` at `:357`).
51
+
52
+ Heuristics:
53
+ - **#1 Visibility of system status** — GOOD: the loop distinguishes *completed*
54
+ from *needs-review* from *failed*, so the UI can tell the user which of three
55
+ very different outcomes occurred instead of a generic "done".
56
+ - **#9 Help users recognize/recover from errors** — GOOD structurally: parse
57
+ slips are traced with a `recovered` flag (`agent.py:255`, `:439-441`), so the
58
+ system can surface "I had to retry" honestly.
59
+ - **Recommendation** — ensure the frontend actually renders the *distinction*
60
+ between `DONE` and `NEEDS_REVIEW` prominently (color + copy), not just the
61
+ `final_message` string; a review-required outcome that looks like success is a
62
+ **#1/#5** hazard (user thinks work shipped when it is only staged).
63
+
64
+ ## Journey 3 — Change approval
65
+
66
+ Surface: `latticeai/services/change_proposals.py`, gate entry
67
+ `latticeai/core/agent.py:273` (`approve`).
68
+
69
+ - Mutations of existing content are **staged, not applied**: `review(...)`
70
+ returns `{"decision": "proposed", ...}` with a proposal record
71
+ (`change_proposals.py:124`, `:177`), and application is a separate explicit
72
+ `approve_and_apply` (`:347`). Additive creates flow with less friction.
73
+
74
+ Heuristics:
75
+ - **#3 User control & freedom** — STRONG: proposal-first governance means a
76
+ destructive/edit action is reversible-by-default (it hasn't happened yet).
77
+ This is the best-defended journey of the five.
78
+ - **#5 Error prevention** — STRONG: the split between additive (`allow_additive`)
79
+ and mutating (`proposed`) is exactly the guardrail #5 asks for.
80
+ - **#10 Help & documentation / #2 match** — RISK: the value only lands if the
81
+ approval UI explains *what changes* and *why it was staged* in plain language.
82
+ Recommend a per-proposal diff + one-line "why this needs your review" derived
83
+ from the `classification`/`reason` provenance already stored
84
+ (`change_proposals.py:237`), so approval is an informed decision, not a blind
85
+ "OK".
86
+
87
+ ## Journey 4 — Brain hierarchy
88
+
89
+ Surface: `frontend/src/features/brain/DepthEmergence.tsx`,
90
+ `.../BrainMemoryLayer.tsx`.
91
+
92
+ - The Brain reveals structure **progressively by depth**: nothing at depth 1,
93
+ memory layer at depth ≥2, knowledge layer at depth ≥3, higher structure at
94
+ depth ≥4 (`DepthEmergence.tsx:29`, `:33-39`); the memory layer caps visible
95
+ nodes by depth (`BrainMemoryLayer.tsx:16`).
96
+
97
+ Heuristics:
98
+ - **#8 Aesthetic & minimalist design** — GOOD intent: progressive disclosure
99
+ avoids dumping the full graph on a new user.
100
+ - **#6 Recognition rather than recall** — RISK: depth is an abstract control. A
101
+ user may not know what "depth 3" means or that concepts appear only there.
102
+ Recommend naming each tier (e.g. "Memories → Concepts → Connections") and
103
+ showing the current tier label, so the hierarchy is recognizable, not a hidden
104
+ numeric mode.
105
+ - **#7 Flexibility & efficiency** — RISK: capping visible nodes (6–8) aids
106
+ focus but can hide relevant memory with no "show more" affordance; verify an
107
+ expansion path exists for power users.
108
+
109
+ ## Journey 5 — Slow first run
110
+
111
+ Surface: `frontend/src/components/onboarding/InstallScreen.tsx`,
112
+ `.../DownloadConsentPanel.tsx`; backing `latticeai/services/model_runtime.py`.
113
+
114
+ - First run is staged and streamed: `InstallStage` = idle→install→download→
115
+ validate→load→done (`InstallScreen.tsx:12`), a live `percent` bar
116
+ (`:28`, `:44`, `:119`) fed by `streamModelPrepare` (`:38`), plus an up-front
117
+ time estimate line (`:86`, `expectedLine` at `:158` using
118
+ `estimatedDownloadMinutes` / `estimatedFirstResponseSeconds`).
119
+ - Download size and consent are disclosed **before** the download begins
120
+ (`DownloadConsentPanel.tsx:9`, `:18-24`).
121
+
122
+ Heuristics:
123
+ - **#1 Visibility of system status** — STRONG: staged progress + percent + ETA
124
+ is close to the textbook remedy for a long wait; the slow first run is
125
+ *communicated*, not silent.
126
+ - **#5 Error prevention / #3 control** — GOOD: size + consent before a large
127
+ download respects the user's bandwidth and choice.
128
+ - **RISK** — the ETA is a static estimate from catalog metadata
129
+ (`estimatedDownloadMinutes`), not a live-adjusted projection; on a slow link
130
+ the bar can stall while the estimate reads optimistically. Recommend deriving
131
+ a live ETA from observed `event.percent` throughput and clearly marking the
132
+ initial number as an estimate. Also confirm a **failure** path exists on the
133
+ `error` stage (`InstallScreen.tsx:12`) with a retry, not a dead end (#9).
134
+
135
+ ## Cross-cutting findings
136
+
137
+ - **#4 Consistency & standards** — i18n has an explicit ko/en fallback discipline
138
+ (e.g. `brain.sources.fallback` in both `frontend/src/i18n/brain.ts:643` and
139
+ `:1301`). Keep en/ko key parity enforced (the repo already has an i18n literal
140
+ check) so no screen silently falls back to Korean for an English user.
141
+ - **#10 Documentation** — onboarding leans on progressive UI over docs, which is
142
+ good, but the abstract controls (Brain depth) would benefit from inline
143
+ first-use hints.
144
+
145
+ ## Priority recommendations (heuristic — validate with real users)
146
+
147
+ 1. **Make `NEEDS_REVIEW` visually distinct from `DONE`** in agent completion
148
+ (Journey 2) — highest risk of a false "it shipped" mental model.
149
+ 2. **Show a diff + plain-language "why staged" on every change proposal**
150
+ (Journey 3) so approval is informed.
151
+ 3. **Name the Brain hierarchy tiers and show the current tier** (Journey 4) to
152
+ convert an abstract depth control into recognizable structure.
153
+ 4. **Live-adjust the first-run ETA and label estimates as estimates**
154
+ (Journeys 1 & 5).
155
+ 5. **Verify the install `error` stage offers a retry** (Journey 5, #9).
156
+
157
+ ## Limitations (honest)
158
+
159
+ - Expert heuristic evaluation only; no real users, no task-success metrics, no
160
+ time-on-task, no accessibility audit (screen-reader/keyboard) beyond noting
161
+ `aria-hidden` usage.
162
+ - Frontend runtime behavior was inferred from source, not driven live in this
163
+ pass; timing/animation claims should be confirmed in a running build.
164
+ - Severity is the evaluator's judgment, not a measured frequency × impact.
@@ -1,6 +1,6 @@
1
1
  # Why Lattice AI Exists
2
2
 
3
- Current release: **9.8.0Honest Knowledge Pipeline**.
3
+ Current release: **9.9.1Clean Foundations**.
4
4
 
5
5
  **Lattice AI is a local-first Digital Brain that keeps your knowledge durable
6
6
  across any AI model.**
@@ -1,8 +1,10 @@
1
1
  # Lattice AI Architecture
2
2
 
3
- > Historical subsystem note. The canonical current architecture document is
3
+ > **Status: historical** — subsystem note preserved for background. The
4
+ > canonical current architecture document is
4
5
  > [`ARCHITECTURE.md`](../ARCHITECTURE.md). This file preserves the v3.6.0
5
- > Knowledge Graph First architecture detail for background.
6
+ > Knowledge Graph First architecture detail; its version and subsystem claims
7
+ > are intentionally frozen at that point in time.
6
8
 
7
9
  > v3.6.0 — **Knowledge Graph First.** Every data source converges into the graph
8
10
  > through one unified ingestion pipeline (`latticeai/services/ingestion.py`), with
package/docs/kg-schema.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Knowledge Graph Schema
2
2
 
3
- Current release: **9.8.0Honest Knowledge Pipeline**.
3
+ Current release: **9.9.1Clean Foundations**.
4
4
 
5
5
  명세 출처: `lattice_ai_full_spec.pptx` 슬라이드 20·21·22
6
- 구현: `kg_schema.py`
6
+ 구현: `lattice_brain/graph/schema.py`
7
7
 
8
8
  ---
9
9
 
@@ -179,8 +179,8 @@ provenance(source_type, source_uri, content_hash, captured_at, modified_at,
179
179
 
180
180
  ## 마이그레이션 (legacy → v2)
181
181
 
182
- 기존 `knowledge_graph.py` 가 만든 `nodes` / `edges` 테이블은 자유 문자열 타입을
183
- 사용해 왔다. `kg_schema.py` 는 매핑 표를 가지고 있어 정식 enum 으로 변환한다.
182
+ 기존 `lattice_brain/graph/store.py`(구 `knowledge_graph.py`) 가 만든 `nodes` / `edges` 테이블은 자유 문자열 타입을
183
+ 사용해 왔다. `lattice_brain/graph/schema.py` 는 매핑 표를 가지고 있어 정식 enum 으로 변환한다.
184
184
 
185
185
  | legacy 타입 (한글 동사) | → v2 `EdgeType` |
186
186
  |------------------------|------------------|
@@ -209,7 +209,7 @@ provenance(source_type, source_uri, content_hash, captured_at, modified_at,
209
209
 
210
210
  마이그레이션은 별도 CLI 없이 **서버 기동 시 자동으로** 일어난다:
211
211
  `knowledge_graph.KnowledgeGraphStore` 가 열릴 때 v2 스키마를 생성/치유하고
212
- (`kg_schema.KGStoreV2.init_schema` — 추가 컬럼은 `ALTER` 로 in-place 치유,
212
+ (`lattice_brain.graph.schema.KGStoreV2.init_schema` — 추가 컬럼은 `ALTER` 로 in-place 치유,
213
213
  edges_v2 식별자 변경은 create→copy→swap 으로 재구축), legacy 데이터를
214
214
  v2 로 백필한다. 기존 `nodes` / `edges` 테이블은 삭제하지 않는다. v4 에서는
215
215
  `nodes_v2` / `edges_v2` 가 쓰기 마스터이며, legacy 테이블은 이전 import/API
@@ -243,7 +243,7 @@ v2 로 백필한다. 기존 `nodes` / `edges` 테이블은 삭제하지 않는
243
243
  read/write 는 `knowledge_graph.KnowledgeGraphStore` 가 담당한다:
244
244
 
245
245
  ```python
246
- from kg_schema import KGStoreV2, NodeType, EdgeType
246
+ from lattice_brain.graph.schema import KGStoreV2, NodeType, EdgeType
247
247
  from knowledge_graph import KnowledgeGraphStore
248
248
 
249
249
  store = KnowledgeGraphStore(db_path, blob_dir)
@@ -1,6 +1,13 @@
1
1
  # Lattice AI — 명세(PPT) vs 구현(repo) 매핑
2
2
 
3
- `lattice_ai_full_spec.pptx` 사양과 현재 리포지토리 구현 사이의 정렬 상태를 장으로 정리한다.
3
+ > **Status: historical** 초기 PPT 명세 대비 갭을 정리한 스냅샷 문서다. 이후
4
+ > 여러 갭이 해소되었다(예: `src-tauri/`의 Tauri 데스크탑 셸, SQLite 라이브
5
+ > 스토어 + 임베딩/벡터 검색, 하이브리드 리트리벌 등). 현행 아키텍처의
6
+ > 단일 진실은 [`../ARCHITECTURE.md`](../ARCHITECTURE.md)와
7
+ > [`../FEATURE_STATUS.md`](../FEATURE_STATUS.md)이며, 아래 갭 서술은
8
+ > 작성 시점 기준으로 보존한다.
9
+
10
+ `lattice_ai_full_spec.pptx` 의 사양과 작성 시점 리포지토리 구현 사이의 정렬 상태를 한 장으로 정리한다.
4
11
  각 항목은 **목표(PPT) → 현재(repo) → 갭 → 보강 위치** 순으로 본다.
5
12
 
6
13
  ---
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "9.8.0"
29
+ __version__ = "9.9.1"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -32,7 +32,7 @@ legacy 테이블에 대한 dual-write 프로젝션(raw SQL) + ``kgv2_*`` 재구
32
32
  사용 예
33
33
  -------
34
34
  ```python
35
- from kg_schema import KGStoreV2
35
+ from lattice_brain.graph.schema import KGStoreV2
36
36
 
37
37
  store = KGStoreV2("/Users/me/.ltcai/kg_v2.db")
38
38
  store.init_schema() # nodes_v2 / edges_v2 생성 + 컬럼 drift self-heal
@@ -48,7 +48,7 @@ from .contracts import multi_agent_contract
48
48
  from ..utils import now_iso as _now
49
49
 
50
50
 
51
- MULTI_AGENT_VERSION = "9.8.0"
51
+ MULTI_AGENT_VERSION = "9.9.1"
52
52
 
53
53
  AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
54
54
  CORE_PIPELINE = ("planner", "executor", "reviewer")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "9.8.0"
3
+ __version__ = "9.9.1"
@@ -19,6 +19,7 @@ from typing import Any, Callable, Dict, List, Optional
19
19
  from fastapi import APIRouter, HTTPException, Request
20
20
  from pydantic import BaseModel, Field
21
21
 
22
+ from latticeai.services.change_proposals import ProposalConflictError
22
23
  from latticeai.services.review_queue import InvalidReviewTransition, ReviewQueueService
23
24
 
24
25
 
@@ -151,6 +152,12 @@ def create_review_queue_router(
151
152
  applied = change_proposals.approve_and_apply(
152
153
  item_id, user_email=user, workspace_id=scope
153
154
  )
155
+ except ProposalConflictError as exc:
156
+ # The file drifted since staging (or the proposal was
157
+ # already resolved): same 409-on-replay semantics as the
158
+ # other illegal transitions, plus a rebase hint so the UI
159
+ # can offer "re-stage against the current content".
160
+ raise HTTPException(status_code=409, detail=exc.to_detail()) from exc
154
161
  except (KeyError, FileNotFoundError) as exc:
155
162
  raise HTTPException(status_code=404, detail=str(exc)) from exc
156
163
  except ValueError as exc:
@@ -44,11 +44,17 @@ class AgentState(str, Enum):
44
44
  VERIFYING = "VERIFYING"
45
45
  FAILED = "FAILED"
46
46
  ROLLBACK = "ROLLBACK"
47
+ # Terminal, non-success: the run ended but completion could not be
48
+ # verified (critic unavailable/unparseable, or a PASS with no execution
49
+ # evidence). Never presented as success — the user must check the result.
50
+ NEEDS_REVIEW = "NEEDS_REVIEW"
47
51
  DONE = "DONE"
48
52
 
49
53
 
50
54
  # Terminal states — the agent loop exits when reaching one of these
51
- AGENT_TERMINAL_STATES: FrozenSet[AgentState] = frozenset({AgentState.DONE, AgentState.FAILED})
55
+ AGENT_TERMINAL_STATES: FrozenSet[AgentState] = frozenset(
56
+ {AgentState.DONE, AgentState.FAILED, AgentState.NEEDS_REVIEW}
57
+ )
52
58
 
53
59
 
54
60
  class AgentRunContext:
@@ -574,11 +580,31 @@ class SingleAgentRuntime:
574
580
  })
575
581
 
576
582
  # ── VERIFY ───────────────────────────────────────────────────────
583
+ def _has_execution_evidence(self, ctx: AgentRunContext) -> bool:
584
+ """Deterministic evidence check: at least one executing step actually
585
+ produced a result (tool ran, or a governed change was staged as a
586
+ proposal). ``final``/parse-error/blocked steps carry no result and do
587
+ not count — a critic PASS over an evidence-free transcript must not
588
+ become DONE."""
589
+ for step in ctx.transcript:
590
+ if step.get("state") != AgentState.EXECUTING.value:
591
+ continue
592
+ if step.get("action") in (None, "final", "parse_error"):
593
+ continue
594
+ if isinstance(step.get("result"), dict):
595
+ return True
596
+ return False
597
+
577
598
  async def verify(
578
599
  self, ctx: AgentRunContext, req: Any, lang_hint: str, current_user: str,
579
600
  max_retry: int = 3, model_id: Optional[str] = None,
580
601
  ) -> None:
581
- """VERIFYING: Critic role evaluates transcript → DONE / EXECUTING (retry) / ROLLBACK / FAILED."""
602
+ """VERIFYING: Critic role evaluates transcript → DONE / EXECUTING (retry) / ROLLBACK / NEEDS_REVIEW / FAILED.
603
+
604
+ Fail-closed: a critic whose output cannot be parsed (after one strict
605
+ repair retry) never fabricates a PASS — the run terminates as
606
+ NEEDS_REVIEW so the user is told to check the result themselves.
607
+ """
582
608
  d = self.deps
583
609
  context = (
584
610
  f"{d.critic_prompt}\n\n"
@@ -593,30 +619,91 @@ class SingleAgentRuntime:
593
619
  context=context, max_tokens=512, temperature=0.1,
594
620
  )
595
621
  ctx.trace.llm_call("verify", model=model_id)
622
+ verdict: Optional[Dict[str, Any]] = None
596
623
  try:
597
624
  verdict, verdict_repairs = extract_action_details(str(raw))
598
625
  ctx.trace.repair("verify", repairs=verdict_repairs)
599
626
  except ValueError as exc:
627
+ # One strict repair retry — re-ask the critic for the exact wire
628
+ # format instead of fabricating a verdict.
600
629
  ctx.trace.parse_error("verify", error=str(exc), recovered=True)
601
- verdict = {"action": "verdict", "verdict": "PASS", "next_state": "DONE",
602
- "reason": "Critic parse failed — assuming pass.", "corrections": [], "confidence": 0.7}
630
+ strict_context = (
631
+ f"{context}\n\n"
632
+ "Your previous verdict was not parseable JSON. Reply with EXACTLY one "
633
+ 'JSON object like {"action": "verdict", "verdict": "PASS", '
634
+ '"next_state": "DONE", "reason": "...", "corrections": []} '
635
+ "and nothing else. verdict must be PASS or FAIL; next_state must be "
636
+ "one of DONE, EXECUTING, ROLLBACK, FAILED."
637
+ )
638
+ raw = await d.generate_as(
639
+ model_id,
640
+ message="Return your verdict as one strict JSON object.",
641
+ context=strict_context, max_tokens=512, temperature=0.0,
642
+ )
643
+ ctx.trace.llm_call("verify", model=model_id)
644
+ try:
645
+ verdict, verdict_repairs = extract_action_details(str(raw))
646
+ ctx.trace.repair("verify", repairs=verdict_repairs)
647
+ except ValueError as retry_exc:
648
+ ctx.trace.parse_error("verify", error=str(retry_exc), recovered=False)
649
+ verdict = None
650
+
651
+ has_evidence = self._has_execution_evidence(ctx)
652
+
653
+ if verdict is None:
654
+ # Verifier unavailable — fail closed, never DONE.
655
+ ctx.transcript.append({
656
+ "state": AgentState.VERIFYING.value,
657
+ "verdict": "UNAVAILABLE",
658
+ "reason": "critic output unparseable after strict retry",
659
+ "verifier_available": False,
660
+ "verdict_valid": False,
661
+ "evidence": has_evidence,
662
+ })
663
+ ctx.trace.decision(
664
+ "verify", decision="verification_unavailable",
665
+ verifier_available=False, verdict_valid=False, evidence=has_evidence,
666
+ )
667
+ ctx.final_message = (
668
+ "검증을 완료하지 못했습니다 — 검증 모델의 응답을 해석할 수 없었습니다. "
669
+ "실행 결과를 직접 확인해 주시고, 필요하면 다시 시도해 주세요."
670
+ )
671
+ ctx.state = AgentState.NEEDS_REVIEW
672
+ return
603
673
 
604
674
  ctx.corrections = verdict.get("corrections", [])
605
675
  # Normalize legacy verdict next_state strings to current AgentState names
606
- raw_next = verdict.get("next_state", "DONE")
676
+ raw_next = verdict.get("next_state", "")
607
677
  next_s = {"COMPLETE": "DONE", "RETRY": "EXECUTING"}.get(raw_next, raw_next)
608
678
 
609
679
  ctx.transcript.append({
610
680
  "state": AgentState.VERIFYING.value,
611
- "verdict": verdict.get("verdict", "PASS"),
681
+ "verdict": verdict.get("verdict", ""),
612
682
  "reason": verdict.get("reason", ""),
613
683
  "corrections": ctx.corrections,
614
684
  "confidence": verdict.get("confidence", 0.9),
615
685
  "next_state": next_s,
686
+ "verifier_available": True,
687
+ "verdict_valid": True,
688
+ "evidence": has_evidence,
616
689
  })
617
690
 
618
- ctx.trace.decision("verify", decision=str(verdict.get("verdict", "PASS")), next_state=next_s)
619
- if verdict.get("verdict") == "PASS" or next_s == "DONE":
691
+ ctx.trace.decision(
692
+ "verify", decision=str(verdict.get("verdict", "")), next_state=next_s,
693
+ verifier_available=True, verdict_valid=True, evidence=has_evidence,
694
+ )
695
+ if verdict.get("verdict") == "PASS":
696
+ # DONE requires both: a validly parsed PASS verdict AND
697
+ # deterministic execution evidence in the transcript. A PASS over
698
+ # an evidence-free run is not a completion.
699
+ if not has_evidence:
700
+ ctx.trace.decision("verify", decision="needs_review_no_evidence")
701
+ ctx.final_message = (
702
+ "검증자는 통과를 보고했지만 실제 실행 근거(도구 실행 기록)가 없어 "
703
+ "완료로 처리하지 않았습니다. 결과를 직접 확인해 주세요."
704
+ )
705
+ ctx.state = AgentState.NEEDS_REVIEW
706
+ return
620
707
  if not ctx.final_message:
621
708
  ctx.final_message = verdict.get("reason", "작업이 완료되었습니다.")
622
709
  ctx.state = AgentState.DONE
@@ -635,6 +722,16 @@ class SingleAgentRuntime:
635
722
  "corrections": ctx.corrections,
636
723
  })
637
724
  ctx.state = AgentState.EXECUTING
725
+ elif next_s == "DONE":
726
+ # Contradictory verdict: the critic asked for DONE without a PASS.
727
+ # The loose "or next_state == DONE" success path is gone — this is
728
+ # a non-success that the user must review.
729
+ ctx.trace.decision("verify", decision="needs_review_inconsistent_verdict")
730
+ ctx.final_message = (
731
+ "검증 결과가 일관되지 않아 완료로 처리하지 않았습니다. "
732
+ "실행 결과를 직접 확인해 주세요."
733
+ )
734
+ ctx.state = AgentState.NEEDS_REVIEW
638
735
  else:
639
736
  ctx.final_message = verdict.get("reason", "검증자가 인식되지 않은 다음 상태를 반환했습니다.")
640
737
  ctx.state = AgentState.FAILED