ltcai 11.6.0 → 11.9.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 (137) hide show
  1. package/README.md +80 -75
  2. package/docs/BENCHMARKS.md +9 -2
  3. package/docs/CHANGELOG.md +201 -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 +34 -14
  7. package/docs/LEGACY_COMPATIBILITY.md +10 -6
  8. package/docs/ONBOARDING.md +5 -3
  9. package/docs/OPERATIONS.md +5 -1
  10. package/docs/PERMISSION_MODE.md +14 -9
  11. package/docs/TRUST_MODEL.md +13 -6
  12. package/docs/USABILITY_AUDIT.md +5 -0
  13. package/docs/WHY_LATTICE.md +6 -4
  14. package/docs/kg-schema.md +7 -3
  15. package/docs/mcp-tools.md +73 -79
  16. package/docs/security-model.md +6 -3
  17. package/lattice_brain/__init__.py +1 -1
  18. package/lattice_brain/graph/_kg_common/__init__.py +1 -54
  19. package/lattice_brain/graph/_kg_common/text.py +14 -450
  20. package/lattice_brain/ingestion/__init__.py +6 -3
  21. package/lattice_brain/multimodal/__init__.py +9 -3
  22. package/latticeai/__init__.py +1 -1
  23. package/latticeai/api/agent_worker_seam.py +12 -1
  24. package/latticeai/api/models.py +18 -110
  25. package/latticeai/api/search.py +7 -30
  26. package/latticeai/api/worker_compute.py +53 -107
  27. package/latticeai/api/worker_seams.py +17 -2
  28. package/latticeai/core/http_origin.py +3 -3
  29. package/latticeai/core/messages.py +0 -5
  30. package/latticeai/core/policy.py +1 -6
  31. package/latticeai/core/quiet.py +1 -20
  32. package/latticeai/core/security.py +29 -83
  33. package/latticeai/core/sessions.py +95 -4
  34. package/latticeai/core/users.py +0 -38
  35. package/latticeai/models/router/catalog.py +2 -2
  36. package/latticeai/models/router/generation.py +81 -14
  37. package/latticeai/models/router/loading.py +41 -5
  38. package/latticeai/runtime/access_runtime.py +7 -4
  39. package/latticeai/runtime/build_phases/features.py +8 -31
  40. package/latticeai/runtime/build_phases/foundation.py +7 -16
  41. package/latticeai/runtime/build_phases/web.py +3 -3
  42. package/latticeai/runtime/build_phases/worker_profile.py +21 -28
  43. package/latticeai/runtime/runtime_context.py +0 -2
  44. package/latticeai/services/architecture_readiness.py +18 -19
  45. package/latticeai/services/process_audit.py +1 -22
  46. package/latticeai/services/product_readiness.py +33 -11
  47. package/latticeai/services/voice_capture.py +8 -28
  48. package/latticeai/tools/__init__.py +6 -46
  49. package/latticeai/tools/commands.py +9 -15
  50. package/latticeai/tools/knowledge.py +0 -6
  51. package/package.json +3 -5
  52. package/requirements.txt +0 -1
  53. package/scripts/check_current_release_docs.mjs +1 -1
  54. package/scripts/check_openapi_drift.mjs +3 -2
  55. package/scripts/check_server_i18n.mjs +5 -4
  56. package/scripts/compose_openapi.py +2 -1
  57. package/scripts/export_openapi.py +5 -4
  58. package/scripts/gen_worker_allowlist_fixture.py +2 -2
  59. package/scripts/openapi_route_families.json +14 -73
  60. package/scripts/release_screen_claims.json +151 -14
  61. package/src-tauri/Cargo.lock +11 -10
  62. package/src-tauri/Cargo.toml +1 -1
  63. package/src-tauri/tauri.conf.json +1 -1
  64. package/static/app/asset-manifest.json +41 -41
  65. package/static/app/assets/{Act-CToZnOHz.js → Act-B4WT81kh.js} +1 -1
  66. package/static/app/assets/AdminConsole--Wf71m-o.js +1 -0
  67. package/static/app/assets/{Brain-C6zCdv4S.js → Brain-CtYaa26c.js} +2 -2
  68. package/static/app/assets/BrainHome-Be2VPJEc.js +2 -0
  69. package/static/app/assets/BrainSignals-C0__xgpG.js +1 -0
  70. package/static/app/assets/Capture-DdNi5Peb.js +1 -0
  71. package/static/app/assets/Chronicle-B3hNveeI.js +1 -0
  72. package/static/app/assets/CommandPalette-CIsnSsFL.js +1 -0
  73. package/static/app/assets/Library-Bl5XClFV.js +1 -0
  74. package/static/app/assets/LivingBrain-DjkTB_gh.js +1 -0
  75. package/static/app/assets/{ProductFlow-CBLdmesT.js → ProductFlow-DCUNRNHs.js} +1 -1
  76. package/static/app/assets/ReviewCard-CD3yWvUB.js +3 -0
  77. package/static/app/assets/System-BJ6jQ_SL.js +1 -0
  78. package/static/app/assets/arrow-left-6_28Z0qH.js +1 -0
  79. package/static/app/assets/{bot-C5-XIww2.js → bot-Bc3Q27YR.js} +1 -1
  80. package/static/app/assets/brain-B9BDrMTe.js +1 -0
  81. package/static/app/assets/{button-DI1KcQP-.js → button-D6JcpYcf.js} +1 -1
  82. package/static/app/assets/circle-check-BFu9lD-3.js +1 -0
  83. package/static/app/assets/{circle-pause-C8HHyood.js → circle-pause-BGMiV8UU.js} +1 -1
  84. package/static/app/assets/{circle-play-Cufc235E.js → circle-play-DoanLHnd.js} +1 -1
  85. package/static/app/assets/{cpu-DW2_JVsv.js → cpu-DwzNf82m.js} +1 -1
  86. package/static/app/assets/{download-gbdKTBb8.js → download-Ddw49yCV.js} +1 -1
  87. package/static/app/assets/{folder-open-C9ydyX33.js → folder-open-Brd6Kvto.js} +1 -1
  88. package/static/app/assets/{hard-drive-BxNMzBM3.js → hard-drive-Bu-DTJdB.js} +1 -1
  89. package/static/app/assets/index-CGdg_aq9.css +2 -0
  90. package/static/app/assets/{index-q9XyDY7A.js → index-CWKRRsLW.js} +4 -4
  91. package/static/app/assets/input-CEqsxtil.js +1 -0
  92. package/static/app/assets/{link-2-B4Y0gz1S.js → link-2-DZ4OA5tJ.js} +1 -1
  93. package/static/app/assets/{permissionCopy-CxrV0fvU.js → permissionCopy-D9TR0F8b.js} +1 -1
  94. package/static/app/assets/primitives-DORg7Z_7.js +1 -0
  95. package/static/app/assets/search-0NQ21wXe.js +1 -0
  96. package/static/app/assets/{share-2-DxArfiuI.js → share-2-BC5FirFv.js} +1 -1
  97. package/static/app/assets/{shield-alert-CgftuFFs.js → shield-alert-DUbR2W2s.js} +1 -1
  98. package/static/app/assets/textarea-jtQcRSXo.js +1 -0
  99. package/static/app/assets/{useFocusTrap-Dzq2j_OG.js → useFocusTrap-HRemcWId.js} +1 -1
  100. package/static/app/assets/{useMutation-BcqfMLNq.js → useMutation-DqlFE-Bw.js} +1 -1
  101. package/static/app/assets/{useQuery-DBzLKCOv.js → useQuery-BizqBNGw.js} +1 -1
  102. package/static/app/assets/utils-WgW4V69R.js +4 -0
  103. package/static/app/assets/workspace-DSek3jCY.js +1 -0
  104. package/static/app/index.html +4 -4
  105. package/static/sw.js +1 -1
  106. package/lattice_brain/ingestion/pipeline.py +0 -108
  107. package/latticeai/api/local_files.py +0 -44
  108. package/latticeai/api/tools.py +0 -126
  109. package/latticeai/api/voice_capture.py +0 -32
  110. package/latticeai/core/agent_permission.py +0 -85
  111. package/scripts/agent_eval.py +0 -34
  112. package/scripts/brain_quality_eval.py +0 -37
  113. package/scripts/check_legacy_debt.mjs +0 -91
  114. package/scripts/check_python.py +0 -100
  115. package/scripts/chunking_parity_corpus.py +0 -449
  116. package/scripts/generate_agent_parity_fixtures.py +0 -771
  117. package/scripts/generate_chunking_parity_fixtures.py +0 -259
  118. package/static/app/assets/AdminConsole-D5kbrBu8.js +0 -1
  119. package/static/app/assets/BrainHome-D4n3EDUt.js +0 -2
  120. package/static/app/assets/BrainSignals-CzeZ-vQ9.js +0 -1
  121. package/static/app/assets/Capture-DPcyTczq.js +0 -1
  122. package/static/app/assets/Chronicle-D3Nx-oWS.js +0 -1
  123. package/static/app/assets/CommandPalette-DPPvpp28.js +0 -1
  124. package/static/app/assets/Library-Co2qTu3I.js +0 -1
  125. package/static/app/assets/LivingBrain-Bsmzwy8_.js +0 -1
  126. package/static/app/assets/ReviewCard-CfR6DLs7.js +0 -3
  127. package/static/app/assets/System-Ce-P2YL6.js +0 -1
  128. package/static/app/assets/arrow-left-DD5jFGHV.js +0 -1
  129. package/static/app/assets/brain-CFRJUvdX.js +0 -1
  130. package/static/app/assets/circle-check-CY20XMCh.js +0 -1
  131. package/static/app/assets/index-BovSWRmQ.css +0 -2
  132. package/static/app/assets/input-GLD94Y_K.js +0 -1
  133. package/static/app/assets/primitives-2ANdAsu2.js +0 -1
  134. package/static/app/assets/search-DTxtjQ07.js +0 -1
  135. package/static/app/assets/textarea-BHVBAoCr.js +0 -1
  136. package/static/app/assets/utils-B-Aah1bO.js +0 -4
  137. package/static/app/assets/workspace-B6fFhoko.js +0 -1
@@ -3,16 +3,18 @@
3
3
  The tail of the build order — everything that mounts a router on the finished
4
4
  application. WP-P1 replaced two phases (``platform_features`` and
5
5
  ``interaction``, ~34 product routers between them) with this one, because the
6
- worker mounts six things:
6
+ worker mounts four things:
7
7
 
8
8
  * ``/health`` — the posture the supervisor gates on;
9
- * the MLX model lifecycle (``/models*``, ``/engines/*``);
10
- * the embedder report (``/api/embeddings/{status,providers}``);
11
- * the document parser (``POST /tools/read_document``, ``GET /tools/pdf_pages``);
12
- * the two capability probes (``/api/ingestion/multimodal``,
13
- ``/api/capture/voice/status``);
9
+ * the MLX model lifecycle (``/models*``, ``/engines/prepare-model*``);
10
+ * the embedder report (``GET /api/embeddings/status``);
14
11
  * the Rust loop's seam (``POST /agent/llm``, ``POST /agent/tool``).
15
12
 
13
+ It mounted three more until v11.8.0 — the ``/tools/*`` document parser, the
14
+ multi-modal capability probe and the voice capability probe. Every one of them
15
+ had no caller in the tree, so the routers, their modules and the construction
16
+ that fed them are gone rather than kept "in case".
17
+
16
18
  The ``/worker/*`` seams are not here: they are mounted by
17
19
  :func:`~latticeai.runtime.build_phases.worker_profile.phase_worker_routes`,
18
20
  which runs after this phase and is the module that owns the worker contract.
@@ -33,22 +35,15 @@ def phase_features(ctx: RuntimeContext) -> None:
33
35
 
34
36
  from latticeai.api.agent_worker_seam import create_agent_worker_seam_router
35
37
  from latticeai.api.health import create_health_router
36
- from latticeai.api.local_files import create_local_files_router
37
38
  from latticeai.api.models import create_models_router
38
39
  from latticeai.api.search import create_search_router
39
- from latticeai.api.tools import create_tools_router
40
- from latticeai.api.voice_capture import create_voice_capture_router
41
40
  from latticeai.core.model_compat import list_cached_profiles
42
41
  from latticeai.runtime.access_runtime import is_externally_reachable
43
42
  from latticeai.runtime.platform_services_runtime import build_model_service
44
43
  from latticeai.services.model_runtime import (
45
44
  ENGINE_MODEL_CATALOG,
46
45
  MODEL_ENGINE_ALIASES,
47
- download_hf_model,
48
- ensure_ollama_server,
49
46
  filter_lower_family_versions,
50
- local_binary,
51
- normalize_local_model_request,
52
47
  sse_event,
53
48
  )
54
49
  from latticeai.services.search_service import SearchService
@@ -81,13 +76,9 @@ def phase_features(ctx: RuntimeContext) -> None:
81
76
  model_router=ctx.model_router,
82
77
  require_user=ctx.require_user,
83
78
  require_admin=ctx.require_admin,
84
- normalize_local_model_request=normalize_local_model_request,
85
- download_hf_model=download_hf_model,
86
79
  prepare_and_load_model=service.prepare_and_load_model,
87
80
  prepare_and_load_model_stream=service.prepare_and_load_model_stream,
88
81
  sse_event=sse_event,
89
- ensure_ollama_server=ensure_ollama_server,
90
- local_binary=local_binary,
91
82
  engine_status=service.engine_status,
92
83
  filter_lower_family_versions=filter_lower_family_versions,
93
84
  list_compat_profiles=list_cached_profiles,
@@ -120,20 +111,6 @@ def phase_features(ctx: RuntimeContext) -> None:
120
111
  )
121
112
  )
122
113
 
123
- app.include_router(
124
- create_tools_router(require_user=ctx.require_user)
125
- )
126
- app.include_router(
127
- create_local_files_router(
128
- require_user=ctx.require_user, ingestion_pipeline=ctx.INGESTION_PIPELINE
129
- )
130
- )
131
- app.include_router(
132
- create_voice_capture_router(
133
- service=ctx.VOICE_CAPTURE, require_user=ctx.require_user
134
- )
135
- )
136
-
137
114
  # AI-Worker seam (v11.5.1, plan §Y1): the calls the Rust agent loop makes
138
115
  # back into Python once orchestration lives in ``lattice-agent``. The tool
139
116
  # route stays behind ``LATTICEAI_AGENT_TOOL_SEAM=1``, which only
@@ -189,16 +189,12 @@ def phase_brain(ctx: RuntimeContext) -> None:
189
189
  """
190
190
  ctx.enter("brain")
191
191
 
192
- from lattice_brain.ingestion import IngestionPipeline
193
192
  from latticeai.core.embedding_providers import (
194
193
  resolve_embedder,
195
194
  resolve_embedding_profile,
196
195
  )
197
196
  from latticeai.runtime.brain_runtime import build_embedder_runtime
198
- from latticeai.services.multimodal_ports import (
199
- build_multimodal_ports,
200
- multimodal_enabled,
201
- )
197
+ from latticeai.services.multimodal_ports import build_multimodal_ports
202
198
  from latticeai.services.voice_capture import VoiceCaptureService
203
199
 
204
200
  # Resolve the configured embedding provider once. Degrades to the offline
@@ -228,18 +224,13 @@ def phase_brain(ctx: RuntimeContext) -> None:
228
224
  # The transcriber is resolved through the voice service so a voice memo and
229
225
  # a scanned ``.m4a`` are transcribed by the same thing — or, far more often,
230
226
  # by the same nothing.
227
+ #
228
+ # The service itself is not published on the context: v11.8.0 deleted its
229
+ # one route (``GET /api/capture/voice/status``, which nothing called), so
230
+ # what the rest of the build needs from it is the port it resolves, and
231
+ # that travels in ``MULTIMODAL_PORTS``.
231
232
  voice_capture = VoiceCaptureService(transcriber=None)
232
233
  multimodal_ports = build_multimodal_ports(
233
234
  transcriber=voice_capture.multimodal_ports().transcriber
234
235
  )
235
- # The capability probe behind ``GET /api/ingestion/multimodal``. It answers
236
- # what this machine *could* do with a picture, a recording or a video; the
237
- # ingest door itself is native (§W3b), which is why nothing is passed here
238
- # for it to write through.
239
- ctx.set(
240
- VOICE_CAPTURE=voice_capture,
241
- MULTIMODAL_PORTS=multimodal_ports,
242
- INGESTION_PIPELINE=IngestionPipeline(
243
- allow_multimodal=multimodal_enabled(), multimodal=multimodal_ports
244
- ),
245
- )
236
+ ctx.set(MULTIMODAL_PORTS=multimodal_ports)
@@ -110,9 +110,9 @@ def build_worker_app_shell(ctx: RuntimeContext):
110
110
  opaque network error.
111
111
  * **The CSRF origin guard** is what makes ``LATTICEAI_CSRF_TRUSTED_ORIGINS``
112
112
  load-bearing for the gateway (v11.6.0 gateway integration §3): a proxied
113
- browser write — ``POST /models/load``, ``POST /engines/pull-model``,
114
- ``POST /tools/read_document`` — arrives carrying the browser's session
115
- cookie *and* ``Origin: …:{gateway port}``, and without the host's
113
+ browser write — ``POST /models/load``, ``POST /engines/prepare-model``,
114
+ ``DELETE /models/unload/{model_id}`` — arrives carrying the browser's
115
+ session cookie *and* ``Origin: …:{gateway port}``, and without the host's
116
116
  injected trust list this guard would answer 403.
117
117
  """
118
118
  from fastapi import FastAPI
@@ -29,12 +29,16 @@ from __future__ import annotations
29
29
 
30
30
  from typing import Any, List, Set, Tuple
31
31
 
32
- #: ``(method, path)`` for each product route v11.6.0 leaves in Python —
32
+ #: ``(method, path)`` for each product route the worker still serves —
33
33
  #: docs/v11.6.0_ONE_DOOR_PLAN.md §최종 Python 워커 표면, cross-checked against
34
34
  #: the KEEP_WORKER table of the route scout, then narrowed by §W3b (ingest,
35
- #: drain/rebuild, voice capture and the four document creators went native) and
36
- #: widened by §4a (the model pull came back). Paths are FastAPI's, converters
37
- #: included, so this compares to ``APIRoute.path`` without normalisation.
35
+ #: drain/rebuild, voice capture and the four document creators went native),
36
+ #: widened by §4a (the model pull came back) and narrowed again by v11.8.0,
37
+ #: which deleted the eight product routes with no caller anywhere in the tree:
38
+ #: the embedding-provider catalogue, both ``/tools/*`` document reads, the
39
+ #: multi-modal and voice capability probes, and three model-lifecycle doors
40
+ #: (see below). Paths are FastAPI's, converters included, so this compares to
41
+ #: ``APIRoute.path`` without normalisation.
38
42
  #:
39
43
  #: This tuple, through :func:`worker_route_keys`, is the **one** source of the
40
44
  #: gateway's proxy allowlist. ``rust/fixtures/worker_allowlist.json`` is its
@@ -53,27 +57,16 @@ WORKER_ROUTES: Tuple[Tuple[str, str], ...] = (
53
57
  ("GET", "/health"),
54
58
  # embedding production (drain/rebuild are native — W3b)
55
59
  ("GET", "/api/embeddings/status"),
56
- ("GET", "/api/embeddings/providers"),
57
- # MLX model + engine lifecycle (in-process only)
60
+ # MLX model + engine lifecycle (in-process only). v11.8.0 dropped
61
+ # ``/models/switch``, ``/models/unload-all`` and ``/engines/pull-model``:
62
+ # nothing in the tree called them, and the surfaces that do this work send
63
+ # ``/models/load`` and ``/engines/prepare-model``, which switch and
64
+ # download as part of what they already do.
58
65
  ("GET", "/models"),
59
66
  ("POST", "/models/load"),
60
- ("POST", "/models/switch/{model_id:path}"),
61
67
  ("DELETE", "/models/unload/{model_id:path}"),
62
- ("DELETE", "/models/unload-all"),
63
68
  ("POST", "/engines/prepare-model"),
64
69
  ("POST", "/engines/prepare-model/stream"),
65
- # The download itself is huggingface_hub / ollama running in *this*
66
- # interpreter, which is the same reason the six lines above stay: model
67
- # management is the worker's box. The gateway proxies it rather than
68
- # shipping a native consent check in front of a 501 (v11.6.0 gateway
69
- # integration §4a).
70
- ("POST", "/engines/pull-model"),
71
- # document parser (create_* + upload are native — W3b)
72
- ("POST", "/tools/read_document"),
73
- ("GET", "/tools/pdf_pages"),
74
- ("GET", "/api/ingestion/multimodal"),
75
- # local ASR status (POST /api/capture/voice is native — W3b)
76
- ("GET", "/api/capture/voice/status"),
77
70
  )
78
71
 
79
72
  #: Retired by W3b — ingest is native. Kept as an empty tuple so imports
@@ -93,8 +86,10 @@ WORKER_SEAM_ROUTES: Tuple[Tuple[str, str], ...] = (
93
86
  #: The pure-compute seams of Wave 2.5 §W2 (``latticeai/api/worker_compute.py``).
94
87
  #: Kept apart from :data:`WORKER_SEAM_ROUTES` because they age in the opposite
95
88
  #: direction: the state seams above are retired once §W3 lands the native write
96
- #: engine, while these nine are what the worker is *for* once Rust owns every
97
- #: write. Mounted here and nowhere else.
89
+ #: engine, while these eight are what the worker is *for* once Rust owns every
90
+ #: write. Mounted here and nowhere else. ``/worker/multimodal/describe`` was the
91
+ #: ninth until v11.8.0 — it described an image for a native image ingest that
92
+ #: was never built, and no caller ever appeared.
98
93
  WORKER_COMPUTE_ROUTES: Tuple[Tuple[str, str], ...] = (
99
94
  ("POST", "/worker/embed"),
100
95
  ("POST", "/worker/parse"),
@@ -103,7 +98,6 @@ WORKER_COMPUTE_ROUTES: Tuple[Tuple[str, str], ...] = (
103
98
  ("POST", "/worker/render/pptx"),
104
99
  ("POST", "/worker/render/pdf"),
105
100
  ("POST", "/worker/asr"),
106
- ("POST", "/worker/multimodal/describe"),
107
101
  ("POST", "/worker/extract"),
108
102
  )
109
103
 
@@ -148,16 +142,15 @@ def phase_worker_routes(ctx: Any) -> None:
148
142
  )
149
143
  )
150
144
  # The compute seams take the *resolved* embedder and the same injected
151
- # multimodal ports the ingestion path holds, so a vector or a caption
152
- # produced through HTTP is the one this process would have produced
153
- # in-process. ``getattr`` because a worker built without a Brain still boots
154
- # — each seam reports the absence rather than failing construction.
145
+ # transcriber the voice path holds, so a vector or a transcript produced
146
+ # through HTTP is the one this process would have produced in-process.
147
+ # ``getattr`` because a worker built without a Brain still boots — each seam
148
+ # reports the absence rather than failing construction.
155
149
  ports = getattr(ctx, "MULTIMODAL_PORTS", None)
156
150
  app.include_router(
157
151
  create_worker_compute_router(
158
152
  embedder=getattr(ctx, "EMBEDDER", None),
159
153
  transcriber=getattr(ports, "transcriber", None),
160
- multimodal_ports=ports,
161
154
  require_user=ctx.require_user,
162
155
  enforce_rate_limit=ctx.enforce_rate_limit,
163
156
  )
@@ -81,9 +81,7 @@ class RuntimeContext:
81
81
  # ── phase 4: the compute ports ───────────────────────────────────────
82
82
  EMBEDDING_PROFILE: Any
83
83
  EMBEDDER: Any
84
- VOICE_CAPTURE: Any
85
84
  MULTIMODAL_PORTS: Any
86
- INGESTION_PIPELINE: Any
87
85
 
88
86
  # ── phase 5: domain singletons ───────────────────────────────────────
89
87
  model_router: Any
@@ -19,7 +19,7 @@ from dataclasses import dataclass
19
19
  from pathlib import Path
20
20
  from typing import Any, Dict, List
21
21
 
22
- ARCHITECTURE_VERSION_TARGET = "11.6.0"
22
+ ARCHITECTURE_VERSION_TARGET = "11.9.0"
23
23
 
24
24
  PREFERRED_REFACTORING_ORDER = [
25
25
  "agent-runtime",
@@ -156,31 +156,28 @@ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
156
156
  ["from fastapi import HTTPException", "raise HTTPException"],
157
157
  )
158
158
  )
159
- # The Python agent loop package is gone. The compatibility alias is now
160
- # scanned on the two Python files that still talk about permission mode.
161
- agent_alias_forbidden = [
162
- finding
163
- for relative_path in (
164
- "latticeai/core/agent_permission.py",
165
- "latticeai/api/agent_worker_seam.py",
166
- )
167
- for finding in _forbidden_patterns(
168
- root,
169
- relative_path,
170
- ["AgentRuntime = SingleAgentRuntime"],
171
- )
172
- ]
159
+ # The Python agent loop package is gone, and 11.8.0 removed the last of its
160
+ # gate tables (``core/agent_permission.py``) once the Rust goldens froze.
161
+ # The compatibility alias is scanned on the one Python file that still
162
+ # talks about permission mode: the worker seam the native loop calls.
163
+ agent_alias_forbidden = _forbidden_patterns(
164
+ root,
165
+ "latticeai/api/agent_worker_seam.py",
166
+ ["AgentRuntime = SingleAgentRuntime"],
167
+ )
173
168
 
174
169
  gates = [
175
170
  ArchitectureGate(
176
171
  id="agent-runtime",
177
172
  title="AgentRuntime boundary",
178
173
  status="complete" if (
179
- _symbol_exists("latticeai.core.agent_permission.block_reason_for_tool")
174
+ _symbol_exists(
175
+ "latticeai.api.agent_worker_seam.create_agent_worker_seam_router"
176
+ )
180
177
  and not agent_alias_forbidden
181
178
  ) else "incomplete",
182
179
  evidence=[
183
- "latticeai.core.agent_permission.block_reason_for_tool",
180
+ "rust/lattice-agent/src/permission.rs block_reason_for_tool (native owner)",
184
181
  "latticeai.api.agent_worker_seam.create_agent_worker_seam_router",
185
182
  "tests/unit/test_agent_worker_seam.py",
186
183
  ],
@@ -256,8 +253,10 @@ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
256
253
  title="Legacy shim sunset plan",
257
254
  status="complete" if shim_report["status"] == "managed" else "incomplete",
258
255
  evidence=[
256
+ # The standalone `check_legacy_debt.mjs` scanner retired once
257
+ # the shim layer hit zero; `legacy_shim_report` *is* the check
258
+ # now, and its status is what this gate reads.
259
259
  "latticeai.services.architecture_readiness.legacy_shim_report",
260
- "scripts/check_legacy_debt.mjs",
261
260
  "tests/unit/test_legacy_root_shims.py",
262
261
  ],
263
262
  ),
@@ -291,7 +290,7 @@ def architecture_readiness(root: Path | None = None) -> Dict[str, Any]:
291
290
  "refactoring_order": list(PREFERRED_REFACTORING_ORDER),
292
291
  "boundaries": {
293
292
  "agent-runtime": {
294
- "owner": "latticeai.core.agent_permission.block_reason_for_tool",
293
+ "owner": "lattice-agent::permission::block_reason_for_tool",
295
294
  "surface": "/agent/llm + /agent/tool",
296
295
  "status": "worker-seam",
297
296
  },
@@ -8,7 +8,7 @@ import os
8
8
  import re
9
9
  import time
10
10
  from pathlib import Path
11
- from typing import Any, Iterable, Mapping, Optional, Sequence
11
+ from typing import Any, Mapping, Optional, Sequence
12
12
 
13
13
  _SECRET_RE = re.compile(
14
14
  r"(api[_-]?key|access[_-]?token|auth[_-]?token|bearer|client[_-]?secret|password|secret|token)",
@@ -104,27 +104,6 @@ def command_plan(
104
104
  }
105
105
 
106
106
 
107
- def command_plan_for_commands(
108
- commands: Iterable[Sequence[Any]],
109
- *,
110
- name: str,
111
- purpose: str = "installer",
112
- cwd: Optional[str] = None,
113
- metadata: Optional[Mapping[str, Any]] = None,
114
- ) -> dict[str, Any]:
115
- normalized = [_normalize_command(command) for command in commands]
116
- digest = _digest_payload({"purpose": purpose, "commands": normalized, "cwd": cwd or ""})
117
- return {
118
- "name": name,
119
- "purpose": purpose,
120
- "command_count": len(normalized),
121
- "command_hash": digest,
122
- "confirmation_token": digest[:16],
123
- "cwd": cwd,
124
- "metadata": dict(metadata or {}),
125
- }
126
-
127
-
128
107
  def verify_command_confirmation(
129
108
  command: Sequence[Any],
130
109
  provided_token: Optional[str],
@@ -7,8 +7,12 @@ evidence that is probed on disk, so a gate only reports ``complete`` when its
7
7
  evidence actually resolves.
8
8
 
9
9
  v11.6.0 retargeted every needle that named a deleted Python product module
10
- onto the worker-only tree, the native owner, or the surviving test. The
11
- assertions themselves were not deleted.
10
+ onto the worker-only tree, the native owner, or the surviving test. 11.8.0 did
11
+ the same for the five needles that named a Python evaluation harness or parity
12
+ generator whose subject is now native: they point at the cargo test or the
13
+ committed golden that actually guards the behaviour. The assertions themselves
14
+ were not deleted — an evidence path is repointed at the owner or it is a gate
15
+ that has stopped meaning anything.
12
16
  """
13
17
 
14
18
  from __future__ import annotations
@@ -19,7 +23,7 @@ from typing import Any, Dict, List
19
23
 
20
24
  from latticeai.services.architecture_readiness import architecture_readiness
21
25
 
22
- PRODUCT_VERSION_TARGET = "11.6.0"
26
+ PRODUCT_VERSION_TARGET = "11.9.0"
23
27
 
24
28
 
25
29
  @dataclass(frozen=True)
@@ -55,7 +59,11 @@ PRODUCT_GATES: List[ProductGate] = [
55
59
  title="Answers carry memory proof and citations",
56
60
  evidence=[
57
61
  "latticeai/api/search.py::embeddings_status",
58
- "scripts/brain_quality_eval.py",
62
+ # 11.8.0: the Python answer-quality harness was deleted with the
63
+ # loop it drove. The committed agent/retrieval goldens are what a
64
+ # proof is checked against now, and they are regenerated by no
65
+ # Python — the manifest is the frozen grid itself.
66
+ "rust/fixtures/golden/manifest.json",
59
67
  ],
60
68
  ),
61
69
  ProductGate(
@@ -66,7 +74,7 @@ PRODUCT_GATES: List[ProductGate] = [
66
74
  "latticeai/tools/documents.py::read_document",
67
75
  "latticeai/tools/filesystem.py::read_file",
68
76
  "latticeai/core/tool_governor.py::classify_tool_call",
69
- "latticeai/core/agent_permission.py::block_reason_for_tool",
77
+ "rust/lattice-agent/src/permission.rs::pub fn block_reason_for_tool",
70
78
  "tests/unit/test_worker_compute.py::test_the_docx_bytes_open_as_a_document_with_the_blocks_that_were_sent",
71
79
  "tests/unit/test_tool_registry.py::test_execute_tool_uses_registry",
72
80
  ],
@@ -111,10 +119,10 @@ PRODUCT_GATES: List[ProductGate] = [
111
119
  f"docs/CHANGELOG.md::## [{PRODUCT_VERSION_TARGET}]",
112
120
  "FEATURE_STATUS.md",
113
121
  f"RELEASE_NOTES_v{PRODUCT_VERSION_TARGET}.md",
114
- "latticeai/core/agent_permission.py::block_reason_for_tool",
122
+ "rust/lattice-agent/src/permission.rs::pub fn block_reason_for_tool",
115
123
  "rust/lattice-agent/src/lib.rs::pub mod agentloop",
116
124
  "latticeai/api/agent_worker_seam.py::create_agent_worker_seam_router",
117
- "latticeai/core/agent_permission.py::non_auto_plan_steps",
125
+ "rust/lattice-agent/src/permission.rs::pub fn non_auto_plan_steps",
118
126
  "latticeai/worker_app.py::create_worker_app",
119
127
  "latticeai/worker_app.py::def create_worker_app",
120
128
  "latticeai/services/architecture_readiness.py::lattice-architecture-contract/v1",
@@ -136,22 +144,36 @@ PRODUCT_GATES: List[ProductGate] = [
136
144
  title="Graph and ingestion integration coverage guards the Brain",
137
145
  evidence=[
138
146
  "tests/unit/test_worker_compute.py::test_extract_returns_the_structures_ingestion_consumes",
139
- "tests/unit/test_worker_compute.py::test_a_picture_comes_back_as_the_facts_the_ingest_path_would_have_written",
147
+ # 11.8.0: `POST /worker/multimodal/describe` was deleted with the
148
+ # rest of the caller-less surface, so its seam test went too. The
149
+ # guarantee this gate needs — that an image becomes the exact facts
150
+ # and metadata an ingest writes — is asserted directly against
151
+ # Brain Core's `extract_image_facts`, which is where the seam only
152
+ # ever forwarded to. Repointed to the surviving equivalent rather
153
+ # than dropped: the gate still has to bite on image ingestion.
154
+ "tests/unit/test_cov_p1b_multimodal.py::test_extract_image_facts_unreadable_skipped_and_full_ports",
140
155
  "tests/unit/test_t3_vision_embedding.py",
141
156
  "tests/unit/test_lattice_brain_isolation.py",
142
- "tests/unit/test_chunking_parity_contract.py",
157
+ # Chunking is native; the parity gate is the cargo test over the
158
+ # committed rust/fixtures/chunking goldens.
159
+ "rust/lattice-ingest/tests/chunking_parity.rs",
143
160
  ],
144
161
  ),
145
162
  ProductGate(
146
163
  id="quality-gates",
147
164
  title="Quality is guarded by repeatable gates",
148
165
  evidence=[
149
- "scripts/brain_quality_eval.py",
166
+ "rust/lattice-retrieval/tests/parity.rs",
150
167
  "scripts/product_readiness.py",
151
168
  "tests/unit/test_product_readiness.py",
152
169
  "tests/visual/v3.surfaces.spec.js::Brain Chat Home",
153
170
  ".github/workflows/ci.yml::scripts/product_readiness.py",
154
- ".github/workflows/release.yml::npm run lint",
171
+ # 11.8.0: the release workflow's lint step folded into the CI leg
172
+ # that already runs it. What this gate has to prove about a release
173
+ # is that a release *runs the readiness gate*, which is the needle
174
+ # below — naming a step by its shell text made the assertion track
175
+ # the workflow's wording rather than its guarantee.
176
+ ".github/workflows/release.yml::scripts/product_readiness.py",
155
177
  ],
156
178
  ),
157
179
  ]
@@ -28,15 +28,18 @@ either, and both say so the same way.
28
28
 
29
29
  v11.6.0 took the *ingest* half away: ``POST /api/capture/voice`` is a native
30
30
  route that stores the memo itself and asks ``POST /worker/asr`` for the words.
31
- What is left is the transcriber and the two questions only this process can
32
- answer about it — can it hear, and what will it accept.
31
+ v11.8.0 took the report away too — ``GET /api/capture/voice/status`` had no
32
+ caller, and a capability answer nobody asks for is a second copy of the same
33
+ facts ``/worker/asr`` already returns per call. What is left is the port
34
+ itself: the transcriber, the two module constants ``/worker/asr`` enforces
35
+ against, and the conversion into the shape Brain Core accepts.
33
36
  """
34
37
 
35
38
  from __future__ import annotations
36
39
 
37
40
  import logging
38
41
  from pathlib import Path
39
- from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
42
+ from typing import TYPE_CHECKING, Callable, Optional
40
43
 
41
44
  if TYPE_CHECKING: # import-time isolation: the port is a callable, not a class
42
45
  from lattice_brain.multimodal import MultimodalPorts
@@ -62,35 +65,12 @@ class TranscriptionUnavailable(RuntimeError):
62
65
 
63
66
 
64
67
  class VoiceCaptureService:
65
- """The local transcriber, and the honest report of whether there is one."""
68
+ """The local transcriber port, and the honest refusal when there is none."""
66
69
 
67
70
  def __init__(
68
- self,
69
- *,
70
- transcriber: Optional[Callable[[str], str]] = None,
71
- max_bytes: int = MAX_AUDIO_BYTES,
71
+ self, *, transcriber: Optional[Callable[[str], str]] = None
72
72
  ) -> None:
73
73
  self._transcriber = transcriber
74
- self._max_bytes = max(1, int(max_bytes))
75
-
76
- # ── capability ───────────────────────────────────────────────────────
77
- def status(self) -> Dict[str, Any]:
78
- """What this install can actually do with a voice memo, honestly."""
79
- return {
80
- # Storing the memo is native (``POST /api/capture/voice`` in
81
- # lattice-platform), so capture is always available to a caller
82
- # that reached this answer through the gateway. Only the
83
- # transcriber is a fact about this process.
84
- "capture": True,
85
- "transcription": self._transcriber is not None,
86
- "supported_extensions": sorted(SUPPORTED_AUDIO_EXTENSIONS),
87
- "max_bytes": self._max_bytes,
88
- "detail": (
89
- "음성 메모를 글로 바꿔 Brain에 저장합니다."
90
- if self._transcriber is not None
91
- else "이 컴퓨터에는 음성 인식기가 없어서, 메모는 저장되지만 글로 바뀌지는 않습니다."
92
- ),
93
- }
94
74
 
95
75
  def multimodal_ports(self) -> "MultimodalPorts":
96
76
  """This service's transcriber, shaped for the ingestion pipeline.
@@ -41,54 +41,14 @@ _PLATFORM = platform.system() # "Darwin" | "Windows" | "Linux"
41
41
  AGENT_ROOT = Path(os.getenv("LATTICEAI_AGENT_ROOT") or "agent_workspace").resolve()
42
42
  MAX_FILE_BYTES = 512_000
43
43
  MAX_COMMAND_SECONDS = 30
44
- MAX_BUILD_SECONDS = 180
45
- MAX_DEPLOY_SECONDS = 300
46
44
  MAX_COMMAND_OUTPUT = 12_000
47
45
 
48
- BLOCKED_COMMANDS = {
49
- "rm",
50
- "rmdir",
51
- "sudo",
52
- "su",
53
- "chmod",
54
- "chown",
55
- "curl",
56
- "wget",
57
- "ssh",
58
- "scp",
59
- "rsync",
60
- "dd",
61
- "mkfs",
62
- "diskutil",
63
- "launchctl",
64
- }
65
-
66
- ALLOWED_COMMANDS = {
67
- "pwd",
68
- "ls",
69
- "find",
70
- "cat",
71
- "head",
72
- "tail",
73
- "wc",
74
- "rg",
75
- "git",
76
- }
77
-
78
- BUILD_SCRIPT_NAMES = {"build", "compile", "typecheck", "test"}
79
- DEPLOY_SCRIPT_NAMES = {
80
- "deploy",
81
- "preview",
82
- "release",
83
- "package",
84
- "dist",
85
- "make",
86
- "build:installer",
87
- "build:pkg",
88
- "build:exe",
89
- "package:mac",
90
- "package:win",
91
- }
46
+ # The build/deploy script allow-lists and their longer timeouts used to live
47
+ # here too. `run_build` / `run_deploy` are native (`lattice-agent`'s
48
+ # `tools::shell`, which carries its own `BUILD_SCRIPT_NAMES`,
49
+ # `DEPLOY_SCRIPT_NAMES`, `MAX_BUILD_SECONDS` and `MAX_DEPLOY_SECONDS`), so the
50
+ # copies here had no reader in this interpreter — a second, unenforced
51
+ # allow-list that could drift from the one that actually gates a subprocess.
92
52
 
93
53
  ALLOWED_GIT_SUBCOMMANDS = {"status", "diff", "log", "show"}
94
54
 
@@ -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:
@@ -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}"