ltcai 11.7.0 → 12.0.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.
Files changed (174) hide show
  1. package/README.md +100 -76
  2. package/docs/BENCHMARKS.md +9 -2
  3. package/docs/CHANGELOG.md +249 -0
  4. package/docs/CI_AND_RELEASE_GATES.md +126 -41
  5. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  6. package/docs/DEVELOPMENT.md +271 -103
  7. package/docs/ENTERPRISE.md +1 -1
  8. package/docs/LEGACY_COMPATIBILITY.md +10 -6
  9. package/docs/MULTI_AGENT_RUNTIME.md +4 -4
  10. package/docs/ONBOARDING.md +16 -4
  11. package/docs/OPERATIONS.md +14 -1
  12. package/docs/PERMISSION_MODE.md +14 -9
  13. package/docs/REALTIME_COLLABORATION.md +1 -1
  14. package/docs/ROADMAP.md +113 -0
  15. package/docs/TRUST_MODEL.md +28 -7
  16. package/docs/USABILITY_AUDIT.md +5 -0
  17. package/docs/WHY_LATTICE.md +13 -5
  18. package/docs/WORKFLOW_DESIGNER.md +2 -2
  19. package/docs/kg-schema.md +57 -7
  20. package/docs/mcp-tools.md +93 -82
  21. package/docs/security-model.md +6 -3
  22. package/lattice_brain/__init__.py +1 -1
  23. package/lattice_brain/graph/_kg_common/__init__.py +1 -54
  24. package/lattice_brain/graph/_kg_common/extraction.py +459 -105
  25. package/lattice_brain/graph/_kg_common/normalize.py +305 -0
  26. package/lattice_brain/graph/_kg_common/patterns.py +275 -0
  27. package/lattice_brain/graph/_kg_common/relations.py +12 -3
  28. package/lattice_brain/graph/_kg_common/sections.py +107 -0
  29. package/lattice_brain/graph/_kg_common/text.py +14 -450
  30. package/lattice_brain/graph/_kg_constants.py +7 -0
  31. package/lattice_brain/ingestion/__init__.py +6 -3
  32. package/lattice_brain/multimodal/__init__.py +9 -3
  33. package/latticeai/__init__.py +1 -1
  34. package/latticeai/api/agent_worker_seam.py +44 -1
  35. package/latticeai/api/models.py +18 -110
  36. package/latticeai/api/search.py +7 -30
  37. package/latticeai/api/worker_compute.py +127 -106
  38. package/latticeai/api/worker_seams.py +17 -2
  39. package/latticeai/core/embedding_providers/__init__.py +16 -0
  40. package/latticeai/core/embedding_providers/autodetect.py +302 -0
  41. package/latticeai/core/embedding_providers/base.py +25 -0
  42. package/latticeai/core/embedding_providers/profiles.py +44 -0
  43. package/latticeai/core/embedding_providers/text.py +74 -8
  44. package/latticeai/core/http_origin.py +3 -3
  45. package/latticeai/core/messages.py +0 -5
  46. package/latticeai/core/policy.py +1 -6
  47. package/latticeai/core/quiet.py +1 -20
  48. package/latticeai/core/security.py +29 -83
  49. package/latticeai/core/sessions.py +95 -4
  50. package/latticeai/core/users.py +0 -38
  51. package/latticeai/core/vector_index/__init__.py +61 -0
  52. package/latticeai/core/vector_index/hnsw.py +383 -0
  53. package/latticeai/core/vector_index/sidecar.py +329 -0
  54. package/latticeai/models/router/catalog.py +2 -2
  55. package/latticeai/models/router/generation.py +176 -30
  56. package/latticeai/models/router/loading.py +150 -9
  57. package/latticeai/runtime/access_runtime.py +7 -4
  58. package/latticeai/runtime/brain_runtime.py +43 -9
  59. package/latticeai/runtime/build_phases/features.py +8 -31
  60. package/latticeai/runtime/build_phases/foundation.py +7 -16
  61. package/latticeai/runtime/build_phases/web.py +3 -3
  62. package/latticeai/runtime/build_phases/worker_profile.py +29 -27
  63. package/latticeai/runtime/runtime_context.py +0 -2
  64. package/latticeai/services/architecture_readiness.py +18 -19
  65. package/latticeai/services/process_audit.py +1 -22
  66. package/latticeai/services/product_readiness.py +39 -12
  67. package/latticeai/services/search_service.py +7 -0
  68. package/latticeai/services/voice_capture.py +8 -28
  69. package/latticeai/tools/__init__.py +12 -47
  70. package/latticeai/tools/commands.py +9 -15
  71. package/latticeai/tools/documents.py +12 -0
  72. package/latticeai/tools/knowledge.py +0 -6
  73. package/latticeai/tools/markup.py +152 -0
  74. package/package.json +4 -5
  75. package/requirements.txt +0 -1
  76. package/scripts/check_current_release_docs.mjs +1 -1
  77. package/scripts/check_openapi_drift.mjs +3 -2
  78. package/scripts/check_server_i18n.mjs +5 -4
  79. package/scripts/compose_openapi.py +4 -1
  80. package/scripts/export_openapi.py +5 -4
  81. package/scripts/gen_worker_allowlist_fixture.py +2 -2
  82. package/scripts/openapi_route_families.json +19 -74
  83. package/scripts/publish_release.mjs +157 -0
  84. package/scripts/release_screen_claims.json +144 -28
  85. package/src-tauri/Cargo.lock +45 -10
  86. package/src-tauri/Cargo.toml +1 -1
  87. package/src-tauri/tauri.conf.json +1 -1
  88. package/static/app/asset-manifest.json +47 -41
  89. package/static/app/assets/Act-Cf1L2709.js +2 -0
  90. package/static/app/assets/AdminConsole-DPAbLTYV.js +1 -0
  91. package/static/app/assets/Brain-DqamGrj-.js +2 -0
  92. package/static/app/assets/BrainHome-MHe2_RYs.js +2 -0
  93. package/static/app/assets/BrainSignals-CQPPfyyH.js +1 -0
  94. package/static/app/assets/Capture-DGdIH_Zc.js +1 -0
  95. package/static/app/assets/Chronicle-C-UlCJoJ.js +1 -0
  96. package/static/app/assets/CommandPalette-WNT4EqUX.js +1 -0
  97. package/static/app/assets/DigitalBrainExplorer-CEBH5Cwc.js +321 -0
  98. package/static/app/assets/Library-C6xd1dlf.js +1 -0
  99. package/static/app/assets/LivingBrain-BEk-0ohw.js +1 -0
  100. package/static/app/assets/ProductFlow-CZLm5iXh.js +1 -0
  101. package/static/app/assets/QueryClientProvider-B3OjqSyJ.js +1 -0
  102. package/static/app/assets/{ReviewCard-HXRle3qq.js → ReviewCard-CEHG6evf.js} +2 -2
  103. package/static/app/assets/RunsListPanel-CLtEJSRW.js +1 -0
  104. package/static/app/assets/System-CAxwBUXw.js +1 -0
  105. package/static/app/assets/WorkflowGraph-Dj10RuGE.js +1 -0
  106. package/static/app/assets/WorkflowsPanel-Kyeh_LIT.js +2 -0
  107. package/static/app/assets/actHelpers-CtSmK9Dw.js +1 -0
  108. package/static/app/assets/arrow-left-CRl5EO4D.js +1 -0
  109. package/static/app/assets/{bot-Cn8bWRuq.js → bot-DhUGRel2.js} +1 -1
  110. package/static/app/assets/brain-CLkhHsHF.js +1 -0
  111. package/static/app/assets/button-CmaEqG1T.js +1 -0
  112. package/static/app/assets/circle-check-CFgejkOS.js +1 -0
  113. package/static/app/assets/{circle-pause-CmzC_apg.js → circle-pause-l96izbxj.js} +1 -1
  114. package/static/app/assets/{circle-play-D8mW2aQ7.js → circle-play-CrZa25_q.js} +1 -1
  115. package/static/app/assets/{cpu-DZcdd0PZ.js → cpu-BaXudqwl.js} +1 -1
  116. package/static/app/assets/{download-bv1KEPGQ.js → download-hCVFPiyc.js} +1 -1
  117. package/static/app/assets/{folder-open-d-Pip5gr.js → folder-open-CHL82Yp7.js} +1 -1
  118. package/static/app/assets/{hard-drive-D20iavUb.js → hard-drive-DDzET7lk.js} +1 -1
  119. package/static/app/assets/index-CB93CZWW.css +2 -0
  120. package/static/app/assets/index-D2H-wSl6.js +13 -0
  121. package/static/app/assets/input-Df1CAY_I.js +1 -0
  122. package/static/app/assets/jsx-runtime-bzQ4Vb5N.js +1 -0
  123. package/static/app/assets/{link-2-BPJOFlAy.js → link-2-xNnTIX1_.js} +1 -1
  124. package/static/app/assets/{permissionCopy-ChdJd493.js → permissionCopy-D3aWHco-.js} +1 -1
  125. package/static/app/assets/primitives-BioD2slS.js +1 -0
  126. package/static/app/assets/search-BzBw8YcW.js +1 -0
  127. package/static/app/assets/{share-2-YNX_NtMU.js → share-2-FkzGf8Df.js} +1 -1
  128. package/static/app/assets/{shield-alert-DuQ3zrVL.js → shield-alert-B3dwzik4.js} +1 -1
  129. package/static/app/assets/sourceMeta-DQSY_tah.js +1 -0
  130. package/static/app/assets/textarea-P8o6pvOP.js +1 -0
  131. package/static/app/assets/useFocusTrap-hswOIkXE.js +1 -0
  132. package/static/app/assets/useMutation-OJLrYSRA.js +1 -0
  133. package/static/app/assets/workspace-BCuk3Ku9.js +1 -0
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/ingestion/pipeline.py +0 -108
  137. package/latticeai/api/local_files.py +0 -44
  138. package/latticeai/api/tools.py +0 -126
  139. package/latticeai/api/voice_capture.py +0 -32
  140. package/latticeai/core/agent_permission.py +0 -85
  141. package/scripts/agent_eval.py +0 -34
  142. package/scripts/brain_quality_eval.py +0 -37
  143. package/scripts/check_legacy_debt.mjs +0 -91
  144. package/scripts/check_python.py +0 -100
  145. package/scripts/chunking_parity_corpus.py +0 -449
  146. package/scripts/generate_agent_parity_fixtures.py +0 -771
  147. package/scripts/generate_chunking_parity_fixtures.py +0 -259
  148. package/static/app/assets/Act-BPcVAbOL.js +0 -1
  149. package/static/app/assets/AdminConsole-Bw1ATQL0.js +0 -1
  150. package/static/app/assets/Brain-CT92Kos0.js +0 -321
  151. package/static/app/assets/BrainHome-CFBkt1K_.js +0 -2
  152. package/static/app/assets/BrainSignals-ReLWF2H8.js +0 -1
  153. package/static/app/assets/Capture-BsTokYkk.js +0 -1
  154. package/static/app/assets/Chronicle-B6f0T9id.js +0 -1
  155. package/static/app/assets/CommandPalette-CuvjTv1u.js +0 -1
  156. package/static/app/assets/Library-BGJbG9Hd.js +0 -1
  157. package/static/app/assets/LivingBrain-DGYK_Jsa.js +0 -1
  158. package/static/app/assets/ProductFlow-DXBC6brE.js +0 -1
  159. package/static/app/assets/System-CMHSO9qM.js +0 -1
  160. package/static/app/assets/arrow-left-BfmkskWx.js +0 -1
  161. package/static/app/assets/brain-CQJberbE.js +0 -1
  162. package/static/app/assets/button-Ct9f2_oT.js +0 -1
  163. package/static/app/assets/circle-check-DruOxB-4.js +0 -1
  164. package/static/app/assets/index-D9x-kSNy.css +0 -2
  165. package/static/app/assets/index-Do83hDzJ.js +0 -10
  166. package/static/app/assets/input-BLXVNmj1.js +0 -1
  167. package/static/app/assets/primitives-Cv5tbZBY.js +0 -1
  168. package/static/app/assets/search-CT9aho2j.js +0 -1
  169. package/static/app/assets/textarea-DqwLnli4.js +0 -1
  170. package/static/app/assets/useFocusTrap-ZVI98jaW.js +0 -1
  171. package/static/app/assets/useMutation-CVC4qv_D.js +0 -1
  172. package/static/app/assets/useQuery-C7BeG4HU.js +0 -1
  173. package/static/app/assets/utils-CiFtIdZq.js +0 -4
  174. package/static/app/assets/workspace-DQz9vIId.js +0 -1
@@ -1,4 +1,12 @@
1
- """Command/build/deploy and read-only git tools (no shell, allow-listed)."""
1
+ """Read-only git tools (no shell, allow-listed subcommands).
2
+
3
+ ``run_command`` and its sandbox left the worker in v11.6.0 §W4 — execution is
4
+ ``lattice-agent``'s. The flag/binary allowlists that only that validator read
5
+ went with it in 11.8.0; what is left here is the four git readers ``POST
6
+ /agent/tool`` still dispatches, and the two argument guards they apply:
7
+ remote targets are refused outright, and a revision may not carry ``..``,
8
+ ``:``, ``/``, ``\\`` or a leading ``-``.
9
+ """
2
10
 
3
11
  from __future__ import annotations
4
12
 
@@ -15,20 +23,6 @@ from latticeai.tools import (
15
23
  _resolve_path,
16
24
  )
17
25
 
18
- # find(1) flags that execute or delete; checked in run_command.
19
- _BLOCKED_FIND_FLAGS = {
20
- "-delete",
21
- "-exec",
22
- "-execdir",
23
- "-fls",
24
- "-fprint",
25
- "-fprintf",
26
- "-ok",
27
- "-okdir",
28
- }
29
- _BLOCKED_RG_FLAGS = {"--pre", "--pre-glob"}
30
- _SAFE_EXECUTABLE_PATH = "/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/usr/local/bin"
31
-
32
26
 
33
27
  def _run_git(args: List[str], cwd: Optional[str] = None) -> Dict[str, Any]:
34
28
  if not args:
@@ -128,6 +128,18 @@ def read_document(path: str) -> Dict[str, Any]:
128
128
  except Exception as exc:
129
129
  raise ToolError(f"PPTX 읽기 실패: {exc}") from exc
130
130
 
131
+ elif ext in {".html", ".htm"}:
132
+ # A web page read verbatim is markup, not text: the graph filled up
133
+ # with `<div class=…>` and the contents of every `<script>`. The
134
+ # conversion keeps the page's headings as `#` lines so a fact
135
+ # extracted from it can still say which section it came from.
136
+ try:
137
+ from latticeai.tools.markup import html_to_text
138
+
139
+ text = html_to_text(target.read_text(encoding="utf-8", errors="replace"))
140
+ except Exception as exc:
141
+ raise ToolError(f"HTML 읽기 실패: {exc}") from exc
142
+
131
143
  elif ext in {".txt", ".md", ".csv"}:
132
144
  try:
133
145
  text = target.read_text(encoding="utf-8", errors="replace")
@@ -11,12 +11,6 @@ from latticeai.services.p_reinforce import BRAIN_DIR, STRUCTURE
11
11
  from latticeai.tools import ToolError
12
12
 
13
13
 
14
- def _safe_brain_folder(folder: str) -> str:
15
- if folder not in STRUCTURE:
16
- raise ToolError(f"Unknown knowledge folder: {folder}")
17
- return folder
18
-
19
-
20
14
  def _scope_digest(kind: str, value: str) -> str:
21
15
  digest = hashlib.sha256(f"{kind}\0{value}".encode("utf-8")).hexdigest()[:24]
22
16
  return f"{kind}-{digest}"
@@ -0,0 +1,152 @@
1
+ """HTML → readable text, with the document's own structure kept.
2
+
3
+ Until v12.0.0 an ``.html`` file was classified as *code* and read verbatim, so
4
+ what reached the graph was `<div class="wrapper">` and the contents of every
5
+ `<script>` tag. Chunks were markup, concepts were attribute names, and a search
6
+ for a sentence on the page could not find the page.
7
+
8
+ The conversion is deliberately small and dependency-free (``html.parser`` and
9
+ ``html.unescape`` are stdlib; adding BeautifulSoup for this would be a new
10
+ runtime dependency for a hundred lines of tag handling):
11
+
12
+ * ``<script>``, ``<style>``, ``<noscript>``, ``<template>`` and everything in
13
+ ``<head>`` except ``<title>`` are dropped — they are not what the page says;
14
+ * ``<h1>``–``<h6>`` become markdown headings, so the section machinery in
15
+ :mod:`lattice_brain.graph._kg_common.sections` gives every extracted fact the
16
+ heading it lived under, exactly as it does for a ``.md`` file;
17
+ * ``<li>`` becomes ``- ``, table cells are tab-separated, and block elements
18
+ end a line, so paragraph and list boundaries survive into chunking;
19
+ * entities are unescaped once, at the end of each text run.
20
+
21
+ The output is plain text that happens to carry markdown headings. It is *not*
22
+ a markdown converter and does not try to be: bold, links and images are read
23
+ for their text, because a knowledge graph wants the sentence, not the styling.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import logging
29
+ import re
30
+ from html import unescape
31
+ from html.parser import HTMLParser
32
+ from typing import List, Set
33
+
34
+ #: Elements whose *content* is never page text.
35
+ SKIPPED_ELEMENTS: Set[str] = {"script", "style", "noscript", "template", "svg"}
36
+ #: Elements that end the current line.
37
+ BLOCK_ELEMENTS: Set[str] = {
38
+ "p", "div", "section", "article", "header", "footer", "nav", "aside",
39
+ "main", "ul", "ol", "li", "table", "tr", "blockquote", "pre", "form",
40
+ "figure", "figcaption", "dl", "dt", "dd", "hr", "br", "h1", "h2", "h3",
41
+ "h4", "h5", "h6", "title",
42
+ }
43
+ #: Elements that separate cells with a tab rather than a newline.
44
+ CELL_ELEMENTS: Set[str] = {"td", "th"}
45
+
46
+ _HEADINGS = {f"h{level}": level for level in range(1, 7)}
47
+ _BLANK_RUN = re.compile(r"\n{3,}")
48
+ _TRAILING_SPACE = re.compile(r"[ \t]+\n")
49
+
50
+
51
+ class _TextExtractor(HTMLParser):
52
+ """Collect a page's readable text, marking its headings."""
53
+
54
+ def __init__(self) -> None:
55
+ super().__init__(convert_charrefs=False)
56
+ self.parts: List[str] = []
57
+ self._skip_depth = 0
58
+ self._heading: int = 0
59
+ self._in_head = False
60
+
61
+ # ── structure ────────────────────────────────────────────────────────
62
+ def handle_starttag(self, tag: str, attrs: object) -> None:
63
+ if tag in SKIPPED_ELEMENTS:
64
+ self._skip_depth += 1
65
+ return
66
+ if tag == "head":
67
+ self._in_head = True
68
+ return
69
+ if self._skip_depth:
70
+ return
71
+ level = _HEADINGS.get(tag)
72
+ # `<title>` is the page's name; treat it as the top heading so the
73
+ # section path of a page with no `<h1>` is still its own title.
74
+ if tag == "title":
75
+ level = 1
76
+ if level:
77
+ self._heading = level
78
+ self.parts.append("\n\n" + "#" * level + " ")
79
+ return
80
+ if tag == "li":
81
+ self.parts.append("\n- ")
82
+ elif tag in CELL_ELEMENTS:
83
+ self.parts.append("\t")
84
+ elif tag in BLOCK_ELEMENTS:
85
+ self.parts.append("\n")
86
+
87
+ def handle_endtag(self, tag: str) -> None:
88
+ if tag in SKIPPED_ELEMENTS:
89
+ self._skip_depth = max(0, self._skip_depth - 1)
90
+ return
91
+ if tag == "head":
92
+ self._in_head = False
93
+ return
94
+ if self._skip_depth:
95
+ return
96
+ if tag in _HEADINGS or tag == "title":
97
+ self._heading = 0
98
+ self.parts.append("\n\n")
99
+ elif tag == "li":
100
+ # `<li>` already opens its own line; closing one too would put a
101
+ # blank line between every bullet and turn a list into a page.
102
+ return
103
+ elif tag in BLOCK_ELEMENTS:
104
+ self.parts.append("\n")
105
+
106
+ # ── text ─────────────────────────────────────────────────────────────
107
+ def handle_data(self, data: str) -> None:
108
+ if self._skip_depth:
109
+ return
110
+ # Inside `<head>`, only `<title>` text is page content; `<meta>` and
111
+ # friends carry no data, but stray whitespace and CDATA do.
112
+ if self._in_head and not self._heading:
113
+ return
114
+ text = unescape(data)
115
+ if self._heading:
116
+ # A heading must stay on one line or it stops being a heading.
117
+ text = " ".join(text.split())
118
+ if text:
119
+ self.parts.append(text)
120
+ return
121
+ cleaned = re.sub(r"[ \t\r\f\v]+", " ", text.replace("\n", " "))
122
+ if cleaned.strip():
123
+ self.parts.append(cleaned)
124
+ elif cleaned:
125
+ self.parts.append(" ")
126
+
127
+ def handle_entityref(self, name: str) -> None:
128
+ self.handle_data(f"&{name};")
129
+
130
+ def handle_charref(self, name: str) -> None:
131
+ self.handle_data(f"&#{name};")
132
+
133
+
134
+ def html_to_text(markup: str) -> str:
135
+ """Readable text for ``markup``, with headings kept as ``#`` lines.
136
+
137
+ Never raises: a truncated or malformed page yields whatever text was
138
+ recoverable, because half a document in the graph beats a failed ingest.
139
+ """
140
+ extractor = _TextExtractor()
141
+ try:
142
+ extractor.feed(str(markup or ""))
143
+ extractor.close()
144
+ except Exception as exc: # pragma: no cover - html.parser is very tolerant
145
+ logging.debug("HTML parse stopped early; keeping what was read: %s", exc)
146
+ text = "".join(extractor.parts)
147
+ text = _TRAILING_SPACE.sub("\n", text)
148
+ text = _BLANK_RUN.sub("\n\n", text)
149
+ return "\n".join(line.rstrip() for line in text.split("\n")).strip()
150
+
151
+
152
+ __all__ = ["BLOCK_ELEMENTS", "CELL_ELEMENTS", "SKIPPED_ELEMENTS", "html_to_text"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "11.7.0",
3
+ "version": "12.0.0",
4
4
  "description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -23,12 +23,10 @@
23
23
  "frontend:openapi:check": "node scripts/check_openapi_drift.mjs",
24
24
  "build:assets": "vite build && node scripts/build_frontend_assets.mjs",
25
25
  "build:python": "node scripts/run_python.mjs -m build",
26
- "check:python": "node scripts/run_python.mjs scripts/check_python.py",
27
- "lint": "npm run lint:python && npm run lint:visual && npm run lint:frontend && npm run frontend:openapi:check && node scripts/check_i18n_literals.mjs && npm run check:i18n-namespaces && npm run check:bundle && npm run check:legacy-debt && npm run check:server-i18n && npm run test:browser-extension && npm run test:vscode-extension && node scripts/check_release_evidence_bound.mjs && npm run check:max-file-lines",
26
+ "lint": "npm run lint:python && npm run lint:visual && npm run lint:frontend && npm run frontend:openapi:check && node scripts/check_i18n_literals.mjs && npm run check:i18n-namespaces && npm run check:bundle && npm run check:server-i18n && node scripts/check_release_evidence_bound.mjs && npm run check:max-file-lines",
28
27
  "lint:visual": "for f in tests/visual/*.js tests/visual/*.cjs tests/visual/mock_server/*.cjs; do node --check \"$f\" || exit 1; done && echo 'visual syntax ok'",
29
28
  "check:max-file-lines": "node scripts/check_max_file_lines.mjs",
30
29
  "check:release-evidence-bound": "node scripts/check_release_evidence_bound.mjs",
31
- "check:legacy-debt": "node scripts/check_legacy_debt.mjs",
32
30
  "check:server-i18n": "node scripts/check_server_i18n.mjs",
33
31
  "lint:python": "node scripts/run_python.mjs -m ruff check . && npm run typecheck:python",
34
32
  "lint:frontend": "node scripts/lint_frontend.mjs",
@@ -36,7 +34,7 @@
36
34
  "docs:check-links": "node scripts/check_markdown_links.mjs && node scripts/check_doc_status.mjs",
37
35
  "docs:check-status": "node scripts/check_doc_status.mjs",
38
36
  "docs:check-current": "node scripts/check_current_release_docs.mjs",
39
- "typecheck": "npm run typecheck:frontend && cd vscode-extension && npm run build",
37
+ "typecheck": "npm run typecheck:frontend",
40
38
  "typecheck:frontend": "npx tsc -p tsconfig.json --noEmit",
41
39
  "test": "node scripts/run_python.mjs -m pytest tests/ -v",
42
40
  "test:frontend": "vitest run",
@@ -59,6 +57,7 @@
59
57
  "release:artifacts": "node scripts/clean_release_artifacts.mjs $npm_package_version && npm run build:assets && npm run build:python && npm pack && npm run package:vsix && npm run desktop:tauri:build",
60
58
  "release:validate": "node scripts/run_python.mjs scripts/validate_release_artifacts.py $npm_package_version --require-vsix --require-tgz --require-dmg",
61
59
  "release:smoke": "node scripts/run_python.mjs scripts/release_smoke.py $npm_package_version",
60
+ "publish:release": "node scripts/publish_release.mjs",
62
61
  "publish:npm": "npm pack && npm publish ltcai-$npm_package_version.tgz --access public",
63
62
  "publish:pypi": "npm run build:python && node scripts/run_python.mjs -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl",
64
63
  "publish:vscode": "cd vscode-extension && npm run package:vsix && npm run publish:vscode",
package/requirements.txt CHANGED
@@ -9,4 +9,3 @@ openpyxl>=3.1,<4
9
9
  python-pptx>=0.6.23,<2
10
10
  keyring>=24,<26
11
11
  pdfplumber>=0.11,<0.12
12
- pypdfium2>=4.30,<6
@@ -6,7 +6,7 @@ const root = process.cwd();
6
6
  const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
7
7
  const version = pkg.version;
8
8
  const releaseDir = `output/release/v${version}`;
9
- const releaseTheme = "Clean Sweep";
9
+ const releaseTheme = "Open House";
10
10
  const title = `${version} — ${releaseTheme}`;
11
11
  const escapedVersion = version.replaceAll(".", "\\.");
12
12
 
@@ -27,8 +27,9 @@ function matches(left, right) {
27
27
  }
28
28
 
29
29
  try {
30
- // P1 cutover: the worker spec is 28 routes. The committed contract is the
31
- // composition of fragments + that worker spec, not the worker dump itself.
30
+ // P1 cutover: the worker spec is 19 routes since v11.8.0 deleted nine that
31
+ // had no caller. The committed contract is the composition of fragments +
32
+ // that worker spec, not the worker dump itself.
32
33
  const workerSpec = join(tempRoot, "worker.json");
33
34
  runNode(join(repo, "scripts", "run_python.mjs"), [
34
35
  "scripts/export_openapi.py",
@@ -24,13 +24,14 @@ import { join } from "node:path";
24
24
  const API_DIR = "latticeai/api";
25
25
 
26
26
  // Routers whose user-facing details are in the message catalog. Append here
27
- // when a router is migrated — never remove. Module names carry no extension:
28
- // `check_legacy_debt.mjs` rejects a bare "<module>.py" literal in scripts/,
29
- // which is how the removed root shims used to be referenced.
27
+ // when a router is migrated. A name is removed only when the router itself is
28
+ // deleted — never to quiet a failure — and the loop below turns a stale name
29
+ // into an error rather than a skip, so the two cases cannot be confused.
30
+ // `tools` left this list in v11.8.0 with `latticeai/api/tools.py`, whose two
31
+ // routes had no caller anywhere in the tree.
30
32
  const LOCALIZED = [
31
33
  "agent_worker_seam",
32
34
  "models",
33
- "tools",
34
35
  "worker_compute",
35
36
  "worker_seams",
36
37
  ];
@@ -119,8 +119,11 @@ INTERNAL_WORKER_PATHS = frozenset(
119
119
  "/worker/render/pptx",
120
120
  "/worker/render/pdf",
121
121
  "/worker/asr",
122
- "/worker/multimodal/describe",
122
+ # ``/worker/multimodal/describe`` was here until v11.8.0 deleted the
123
+ # seam: it described an image for a native ingest nothing ever built.
123
124
  "/worker/extract",
125
+ # v12.0.0 — HNSW sidecar query. Same loopback-only reasoning.
126
+ "/worker/vector/query",
124
127
  }
125
128
  )
126
129
 
@@ -3,9 +3,10 @@
3
3
 
4
4
  Until v11.6.0 this *was* the contract: it built the 464-route product app and
5
5
  wrote ``frontend/openapi.json``. WP-P1 deleted that app. The committed contract
6
- is unchanged (421 paths / 463 operations — clients still call all of them) but
7
- its source is now ``scripts/compose_openapi.py``, which reassembles the
8
- per-crate fragments in ``rust/fixtures/openapi/``.
6
+ kept describing every route clients call (421 paths / 463 operations through
7
+ v11.7.0; 413 / 455 since v11.8.0 removed eight routes nothing called) but its
8
+ source is now ``scripts/compose_openapi.py``, which reassembles the per-crate
9
+ fragments in ``rust/fixtures/openapi/``.
9
10
 
10
11
  What this script exports is the worker half, to a **scratch** path, so the
11
12
  composer can check it: every operation the worker serves must already be in
@@ -90,7 +91,7 @@ def isolated_runtime_environment(root: Path) -> Iterator[Mapping[str, str]]:
90
91
 
91
92
  def main() -> int:
92
93
  # No default of ``frontend/openapi.json``: that file is the composer's
93
- # output now, and silently overwriting it with the 28-route worker spec is
94
+ # output now, and silently overwriting it with the 19-route worker spec is
94
95
  # exactly the mistake this argument exists to prevent.
95
96
  if len(sys.argv) < 2:
96
97
  print(
@@ -10,10 +10,10 @@ time.
10
10
  Two spellings per route, because the two runtimes disagree about path syntax
11
11
  and only one of them can be the file's:
12
12
 
13
- * ``path`` is FastAPI's, converters included (``/models/switch/{model_id:path}``).
13
+ * ``path`` is FastAPI's, converters included (``/models/unload/{model_id:path}``).
14
14
  It is what ``APIRoute.path`` returns, so the Python side compares without
15
15
  normalisation.
16
- * ``axum`` is the same route as axum 0.7 spells it (``/models/switch/*model_id``).
16
+ * ``axum`` is the same route as axum 0.7 spells it (``/models/unload/*model_id``).
17
17
  A ``{name:path}`` converter matches slashes; a plain ``:name`` capture does
18
18
  not, so an id containing a ``/`` would 404 at the front door while working
19
19
  against the worker directly. WP-I5's ``greedy_path_params`` records the same
@@ -11,13 +11,15 @@
11
11
  "that stays in the Python AI worker and is the gateway's proxy allowlist.",
12
12
  "'greedy_path_params' records the FastAPI '{name:path}' converters the schema",
13
13
  "cannot show: those segments match slashes, so an axum port needs a wildcard",
14
- "('/*name' in axum 0.7) rather than a plain ':name' capture."
14
+ "('/*name' in axum 0.7) rather than a plain ':name' capture.",
15
+ "v11.8.0 deleted eight caller-less worker routes end-to-end; their entries",
16
+ "left this map, the committed spec and every fragment together."
15
17
  ],
16
18
  "source": {
17
19
  "spec": "frontend/openapi.json",
18
- "spec_version": "11.5.2",
19
- "paths": 421,
20
- "operations": 463,
20
+ "spec_version": "11.8.0",
21
+ "paths": 415,
22
+ "operations": 457,
21
23
  "classification": "scout_routes.md (v11.6.0 route inventory) + docs/v11.6.0_ONE_DOOR_PLAN.md",
22
24
  "module_attribution": "FastAPI route.endpoint.__module__ of the app built by scripts/export_openapi.py"
23
25
  },
@@ -56,7 +58,7 @@
56
58
  "wp": "R4",
57
59
  "target": "lattice-chat",
58
60
  "summary": "Chat orchestration, chat history and the agent-loop HTTP surface.",
59
- "operations": 11
61
+ "operations": 12
60
62
  },
61
63
  "memory_brain": {
62
64
  "wp": "R5",
@@ -80,7 +82,7 @@
80
82
  "wp": "R8",
81
83
  "target": "lattice-platform / lattice-agent",
82
84
  "summary": "MCP, marketplace, plugin SDK, agent registry, multi-agent runtime and the direct tool surface.",
83
- "operations": 86
85
+ "operations": 87
84
86
  },
85
87
  "portability_network": {
86
88
  "wp": "R9",
@@ -97,8 +99,8 @@
97
99
  "worker_keep": {
98
100
  "wp": "\u2014",
99
101
  "target": "Python AI worker (proxy allowlist)",
100
- "summary": "The 25 KEEP_WORKER routes from scout_routes.md, the graph single-writer seam, and POST /engines/pull-model: the surface whose handler stays inside the Python worker's interpreter.",
101
- "operations": 27
102
+ "summary": "The surface whose handler stays inside the Python worker's interpreter: scout_routes.md's KEEP_WORKER set and the graph single-writer seam, less the eight caller-less routes v11.8.0 deleted end-to-end (embeddings providers, the two document-tool reads, the multimodal and voice-status probes, model switch/unload-all and engine pull-model).",
103
+ "operations": 19
102
104
  }
103
105
  },
104
106
  "greedy_path_params": {
@@ -114,8 +116,7 @@
114
116
  "GET /knowledge-graph/neighbors/{node_id}": "node_id",
115
117
  "GET /workspace/relationships/{node_id}": "node_id",
116
118
  "PATCH /admin/users/{email}": "email",
117
- "PATCH /agents/api/registry/{agent_id}": "agent_id",
118
- "POST /models/switch/{model_id}": "model_id"
119
+ "PATCH /agents/api/registry/{agent_id}": "agent_id"
119
120
  },
120
121
  "module_family": {
121
122
  "latticeai.api.admin": "admin",
@@ -167,10 +168,6 @@
167
168
  "latticeai.services.local_knowledge": "knowledge_search"
168
169
  },
169
170
  "overrides": {
170
- "DELETE /models/unload-all": {
171
- "family": "worker_keep",
172
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
173
- },
174
171
  "DELETE /models/unload/{model_id}": {
175
172
  "family": "worker_keep",
176
173
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
@@ -183,22 +180,10 @@
183
180
  "family": "ui_redirects",
184
181
  "reason": "scout_routes.md classifies this route STATIC: an SPA page shell the gateway answers (WP-I4)."
185
182
  },
186
- "GET /api/capture/voice/status": {
187
- "family": "worker_keep",
188
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
189
- },
190
- "GET /api/embeddings/providers": {
191
- "family": "worker_keep",
192
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
193
- },
194
183
  "GET /api/embeddings/status": {
195
184
  "family": "worker_keep",
196
185
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
197
186
  },
198
- "GET /api/ingestion/multimodal": {
199
- "family": "worker_keep",
200
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
201
- },
202
187
  "GET /graph": {
203
188
  "family": "ui_redirects",
204
189
  "reason": "scout_routes.md classifies this route STATIC: an SPA page shell the gateway answers (WP-I4)."
@@ -219,10 +204,6 @@
219
204
  "family": "ui_redirects",
220
205
  "reason": "scout_routes.md classifies this route STATIC: an SPA page shell the gateway answers (WP-I4)."
221
206
  },
222
- "GET /tools/pdf_pages": {
223
- "family": "worker_keep",
224
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
225
- },
226
207
  "GET /workflows": {
227
208
  "family": "ui_redirects",
228
209
  "reason": "scout_routes.md classifies this route STATIC: an SPA page shell the gateway answers (WP-I4)."
@@ -263,10 +244,6 @@
263
244
  "family": "worker_keep",
264
245
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
265
246
  },
266
- "POST /engines/pull-model": {
267
- "family": "worker_keep",
268
- "reason": "The pull itself is huggingface_hub / ollama running inside the worker's interpreter; the gateway proxies it rather than shipping a native 501 (v11.6.0 gateway integration \u00a74a)."
269
- },
270
247
  "POST /knowledge-graph/ingest": {
271
248
  "family": "worker_keep",
272
249
  "reason": "v11.6.0 plan, '\ucd5c\uc885 Python \uc6cc\ucee4 \ud45c\uba74': the graph single-writer seam stays with the worker even though scout_routes.md scores it PORT_ORCH."
@@ -275,10 +252,6 @@
275
252
  "family": "worker_keep",
276
253
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
277
254
  },
278
- "POST /models/switch/{model_id}": {
279
- "family": "worker_keep",
280
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
281
- },
282
255
  "POST /tools/create_docx": {
283
256
  "family": "worker_keep",
284
257
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
@@ -295,10 +268,6 @@
295
268
  "family": "worker_keep",
296
269
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
297
270
  },
298
- "POST /tools/read_document": {
299
- "family": "worker_keep",
300
- "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
301
- },
302
271
  "POST /upload/document": {
303
272
  "family": "worker_keep",
304
273
  "reason": "scout_routes.md classifies this route KEEP_WORKER (AI-worker box)."
@@ -345,10 +314,6 @@
345
314
  "module": "latticeai.api.mcp",
346
315
  "family": "mcp_market"
347
316
  },
348
- "DELETE /models/unload-all": {
349
- "module": "latticeai.api.models",
350
- "family": "worker_keep"
351
- },
352
317
  "DELETE /models/unload/{model_id}": {
353
318
  "module": "latticeai.api.models",
354
319
  "family": "worker_keep"
@@ -605,10 +570,6 @@
605
570
  "module": "latticeai.api.brain_intelligence",
606
571
  "family": "memory_brain"
607
572
  },
608
- "GET /api/capture/voice/status": {
609
- "module": "latticeai.api.voice_capture",
610
- "family": "worker_keep"
611
- },
612
573
  "GET /api/chronicle/as-of": {
613
574
  "module": "latticeai.api.chronicle",
614
575
  "family": "memory_brain"
@@ -621,6 +582,10 @@
621
582
  "module": "latticeai.api.chronicle",
622
583
  "family": "memory_brain"
623
584
  },
585
+ "GET /api/cloud/status": {
586
+ "module": "latticeai.api.chat",
587
+ "family": "chat"
588
+ },
624
589
  "GET /api/command/briefing": {
625
590
  "module": "latticeai.api.command_center",
626
591
  "family": "memory_brain"
@@ -629,10 +594,6 @@
629
594
  "module": "latticeai.api.command_center",
630
595
  "family": "memory_brain"
631
596
  },
632
- "GET /api/embeddings/providers": {
633
- "module": "latticeai.api.search",
634
- "family": "worker_keep"
635
- },
636
597
  "GET /api/embeddings/status": {
637
598
  "module": "latticeai.api.search",
638
599
  "family": "worker_keep"
@@ -685,10 +646,6 @@
685
646
  "module": "latticeai.api.local_files",
686
647
  "family": "knowledge_search"
687
648
  },
688
- "GET /api/ingestion/multimodal": {
689
- "module": "latticeai.api.local_files",
690
- "family": "worker_keep"
691
- },
692
649
  "GET /api/ingestion/watch": {
693
650
  "module": "latticeai.api.local_files",
694
651
  "family": "knowledge_search"
@@ -1129,10 +1086,6 @@
1129
1086
  "module": "latticeai.api.tools",
1130
1087
  "family": "mcp_market"
1131
1088
  },
1132
- "GET /tools/pdf_pages": {
1133
- "module": "latticeai.api.tools",
1134
- "family": "worker_keep"
1135
- },
1136
1089
  "GET /tools/permissions": {
1137
1090
  "module": "latticeai.api.tools",
1138
1091
  "family": "mcp_market"
@@ -1709,10 +1662,6 @@
1709
1662
  "module": "latticeai.api.models",
1710
1663
  "family": "worker_keep"
1711
1664
  },
1712
- "POST /engines/pull-model": {
1713
- "module": "latticeai.api.models",
1714
- "family": "worker_keep"
1715
- },
1716
1665
  "POST /engines/verify-cloud": {
1717
1666
  "module": "latticeai.api.models",
1718
1667
  "family": "models_misc"
@@ -1797,6 +1746,10 @@
1797
1746
  "module": "latticeai.api.marketplace",
1798
1747
  "family": "mcp_market"
1799
1748
  },
1749
+ "POST /mcp": {
1750
+ "module": "latticeai.api.mcp",
1751
+ "family": "mcp_market"
1752
+ },
1800
1753
  "POST /mcp/call": {
1801
1754
  "module": "latticeai.api.mcp",
1802
1755
  "family": "mcp_market"
@@ -1821,10 +1774,6 @@
1821
1774
  "module": "latticeai.api.models",
1822
1775
  "family": "worker_keep"
1823
1776
  },
1824
- "POST /models/switch/{model_id}": {
1825
- "module": "latticeai.api.models",
1826
- "family": "worker_keep"
1827
- },
1828
1777
  "POST /network/peers": {
1829
1778
  "module": "latticeai.api.network",
1830
1779
  "family": "portability_network"
@@ -1985,10 +1934,6 @@
1985
1934
  "module": "latticeai.api.tools",
1986
1935
  "family": "mcp_market"
1987
1936
  },
1988
- "POST /tools/read_document": {
1989
- "module": "latticeai.api.tools",
1990
- "family": "worker_keep"
1991
- },
1992
1937
  "POST /tools/read_file": {
1993
1938
  "module": "latticeai.api.tools",
1994
1939
  "family": "mcp_market"