ltcai 11.5.0 → 11.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +25 -18
  2. package/docs/CHANGELOG.md +22 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +1 -1
  7. package/docs/TRUST_MODEL.md +1 -1
  8. package/docs/WHY_LATTICE.md +1 -1
  9. package/docs/kg-schema.md +1 -1
  10. package/docs/v11.5.1_RUST_FULL_LOOP_PLAN.md +85 -0
  11. package/lattice_brain/__init__.py +1 -1
  12. package/lattice_brain/runtime/multi_agent.py +1 -1
  13. package/latticeai/__init__.py +1 -1
  14. package/latticeai/api/agent_worker_seam.py +389 -0
  15. package/latticeai/core/legacy_compatibility.py +1 -1
  16. package/latticeai/core/marketplace.py +1 -1
  17. package/latticeai/core/messages.py +35 -0
  18. package/latticeai/core/workspace_os_constants.py +1 -1
  19. package/latticeai/runtime/build_phases/features.py +23 -2
  20. package/latticeai/services/architecture_readiness.py +1 -1
  21. package/latticeai/services/product_readiness.py +1 -1
  22. package/package.json +1 -1
  23. package/scripts/check_current_release_docs.mjs +1 -1
  24. package/scripts/check_server_i18n.mjs +1 -0
  25. package/scripts/generate_agent_loop_fixtures.py +907 -0
  26. package/scripts/generate_rust_parity_fixtures.py +70 -67
  27. package/scripts/parity_fixture_corpus_docgen.py +341 -0
  28. package/scripts/release_screen_claims.json +10 -0
  29. package/src-tauri/Cargo.lock +13 -7
  30. package/src-tauri/Cargo.toml +1 -1
  31. package/src-tauri/tauri.conf.json +1 -1
  32. package/static/app/asset-manifest.json +41 -41
  33. package/static/app/assets/{Act-CWnxSCgN.js → Act-Drs_jd-O.js} +1 -1
  34. package/static/app/assets/{AdminConsole-BEQYU6kF.js → AdminConsole-BqNx5oF6.js} +1 -1
  35. package/static/app/assets/{Brain-DWu1BhFg.js → Brain-C7bdNmz-.js} +1 -1
  36. package/static/app/assets/{BrainHome-95Hilr9R.js → BrainHome-DcTnINcI.js} +1 -1
  37. package/static/app/assets/{BrainSignals-QdeqCpAF.js → BrainSignals-Df1MRIPD.js} +1 -1
  38. package/static/app/assets/{Capture-BHpCxnzb.js → Capture-Dgsxvnbx.js} +1 -1
  39. package/static/app/assets/{Chronicle-B4xYKoed.js → Chronicle-BrkTxJK1.js} +1 -1
  40. package/static/app/assets/{CommandPalette-BVXnttSz.js → CommandPalette-DDziw7Oj.js} +1 -1
  41. package/static/app/assets/{Library-DgYcHome.js → Library-CYxeuraA.js} +1 -1
  42. package/static/app/assets/{LivingBrain-CrJLDbf7.js → LivingBrain-CNp6mvCm.js} +1 -1
  43. package/static/app/assets/{ProductFlow-DFlScKoJ.js → ProductFlow-BVsowu3Z.js} +1 -1
  44. package/static/app/assets/{ReviewCard-Cy5f48Pj.js → ReviewCard-D1LbOfJS.js} +1 -1
  45. package/static/app/assets/{System-NF8IfhTa.js → System-DcRAq7wj.js} +1 -1
  46. package/static/app/assets/arrow-left-BHYmOTWU.js +1 -0
  47. package/static/app/assets/{bot-CucuhLhm.js → bot-BK2xQ9mN.js} +1 -1
  48. package/static/app/assets/{brain-BBnSryW_.js → brain-BZNztFBb.js} +1 -1
  49. package/static/app/assets/{button-C2GUj2Ai.js → button-CjueubVZ.js} +1 -1
  50. package/static/app/assets/circle-check-CLeWJr67.js +1 -0
  51. package/static/app/assets/{circle-pause-CbkWzBmG.js → circle-pause-HRjSmpyY.js} +1 -1
  52. package/static/app/assets/{circle-play-7lEaqHdJ.js → circle-play-CMcKDVwu.js} +1 -1
  53. package/static/app/assets/{cpu-DAlCXlIy.js → cpu-B_MyTfYr.js} +1 -1
  54. package/static/app/assets/{download-RNhuuJwh.js → download-CTy0EByV.js} +1 -1
  55. package/static/app/assets/{folder-open-CLW4odzM.js → folder-open-DF6bLhTg.js} +1 -1
  56. package/static/app/assets/{hard-drive-NKEiDIAJ.js → hard-drive-CpWHG77C.js} +1 -1
  57. package/static/app/assets/{index-DMurvUuR.js → index-Dd6abJHX.js} +3 -3
  58. package/static/app/assets/index-DxmOfNRi.css +2 -0
  59. package/static/app/assets/{input-D2UhPC1X.js → input-bVgIRN3s.js} +1 -1
  60. package/static/app/assets/{link-2-6amKbP_P.js → link-2-bg6CG5Vk.js} +1 -1
  61. package/static/app/assets/{permissionCopy-Cu9TZtdR.js → permissionCopy-DFTf1HDZ.js} +1 -1
  62. package/static/app/assets/{primitives-gPsccucr.js → primitives-D7D-sQ3T.js} +1 -1
  63. package/static/app/assets/search-BgSVX6NG.js +1 -0
  64. package/static/app/assets/{share-2-Bau7KkPq.js → share-2-BLzo7U4L.js} +1 -1
  65. package/static/app/assets/{shield-alert-BufNYypi.js → shield-alert-BmngqnsJ.js} +1 -1
  66. package/static/app/assets/{textarea-BQnVWhYs.js → textarea-DOSlAQQ3.js} +1 -1
  67. package/static/app/assets/{useFocusTrap-B3_w60si.js → useFocusTrap-pZhgeee4.js} +1 -1
  68. package/static/app/assets/{useMutation-BHhCflT6.js → useMutation-BMDwNk4I.js} +1 -1
  69. package/static/app/assets/{useQuery-rBWfI-5t.js → useQuery-CSjKttRo.js} +1 -1
  70. package/static/app/assets/{utils-V_5-wxr5.js → utils-D3u_yv7B.js} +1 -1
  71. package/static/app/assets/{workspace-K1zjYUHj.js → workspace-H3bMjYXC.js} +1 -1
  72. package/static/app/index.html +4 -4
  73. package/static/sw.js +1 -1
  74. package/static/app/assets/arrow-left-DwkSYrjR.js +0 -1
  75. package/static/app/assets/circle-check-CxOVPwYq.js +0 -1
  76. package/static/app/assets/index-BLPb5lmE.css +0 -2
  77. package/static/app/assets/search-Cj_TKk_2.js +0 -1
@@ -6,27 +6,23 @@ something keeps proving it is still one. This script is the Python half of that
6
6
  proof: it builds a small, fully deterministic Brain with the **real** write path
7
7
  (``KnowledgeGraphStore._upsert_node`` / ``_upsert_chunk`` / ``_upsert_edge``,
8
8
  the real hash embedder, the real v2 projection and trigram FTS index), then runs
9
- the real ``hybrid_search`` / ``search`` / ``vector_search`` over it and writes
10
- their answers to ``rust/fixtures/golden/``.
11
-
12
- v11.5.0 widens it past search: the same store also carries the conversation
13
- corpus, and the same generator drives the KG relationship/traverse reads, the
14
- service-layer graph and three-channel hybrid, the durable history reads and the
15
- context assembler (:data:`SUITES`).
16
-
17
- Two consumers read what it writes:
18
-
19
- * ``tests/unit/test_rust_parity_contract.py`` re-runs the Python engines against
20
- the committed database and asserts the goldens still hold — so a change to
21
- Python semantics fails loudly instead of silently invalidating the contract
22
- the Rust side is pinned to;
23
- * ``rust/lattice-retrieval/tests/parity.rs`` runs the Rust port against the same
24
- database and the same goldens.
25
-
26
- Determinism is the whole design constraint: every timestamp is written by the
27
- real code and then **backdated**; the two ``datetime.now()`` calls the ports
28
- reach are frozen at :data:`FROZEN_NOW` (recorded in the manifest); LLM concept
29
- extraction is forced off; every environment knob is pinned to its default.
9
+ the real engines over it and writes their answers to ``rust/fixtures/golden/``.
10
+ The suite set widens with the port (:data:`SUITES`): the search engines
11
+ (v11.4.0); the KG relationship/traverse reads, the service layer, the durable
12
+ history reads and the context assembler (v11.5.0); the document-generation
13
+ search, traversal and context builder (v11.5.1, whose corpus and runners live
14
+ in ``scripts/parity_fixture_corpus_docgen.py``).
15
+
16
+ Two consumers read what it writes: ``tests/unit/test_rust_parity_contract.py``
17
+ re-runs the Python entry points against the committed database and asserts the
18
+ goldens still hold — so a change to Python semantics fails loudly instead of
19
+ silently invalidating the contract the Rust side is pinned to — and
20
+ ``rust/lattice-retrieval/tests/{parity,suites}.rs`` run the Rust ports against
21
+ the same database and the same goldens. Determinism is the whole design
22
+ constraint: every timestamp is written by the real code and then **backdated**;
23
+ every ``datetime.now()`` the ports reach is frozen at :data:`FROZEN_NOW`
24
+ (recorded in the manifest); LLM concept extraction is forced off; every
25
+ environment knob is pinned to its default.
30
26
 
31
27
  Usage::
32
28
 
@@ -47,8 +43,14 @@ from pathlib import Path
47
43
  from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
48
44
 
49
45
  REPO_ROOT = Path(__file__).resolve().parents[1]
50
- if str(REPO_ROOT) not in sys.path:
51
- sys.path.insert(0, str(REPO_ROOT))
46
+ # The repo (for the product packages) and this directory: `scripts` is not a
47
+ # package, so the docgen corpus is imported by name off the script directory —
48
+ # the same by-path convention the contract test uses to import this file.
49
+ for _import_root in (REPO_ROOT, Path(__file__).resolve().parent):
50
+ if str(_import_root) not in sys.path:
51
+ sys.path.insert(0, str(_import_root))
52
+
53
+ import parity_fixture_corpus_docgen as docgen # noqa: E402
52
54
 
53
55
  FIXTURE_DIR = REPO_ROOT / "rust" / "fixtures"
54
56
  GOLDEN_DIR = FIXTURE_DIR / "golden"
@@ -80,11 +82,11 @@ WS_BETA = "ws-beta"
80
82
  # (node_id, type, title, summary, metadata, workspace_id, updated_at)
81
83
  #
82
84
  # Shaped on purpose: every ``type_boost`` type appears and so do types outside it;
83
- # titles/summaries are half Korean (the tokenizer, classifier and extractor all
84
- # branch on script); the ``tie:`` block is five rows sharing one timestamp, one
85
- # type and nothing to match, pinning the (hits, boost, updated_at) → id ASC
86
- # tie-break; two workspaces plus NULL-workspace rows cover all three scoping
87
- # answers (no scoping / empty set / a specific workspace).
85
+ # titles/summaries are half Korean (tokenizer, classifier and extractor all branch
86
+ # on script); the ``tie:`` block is five rows sharing one timestamp, one type and
87
+ # nothing to match, pinning the (hits, boost, updated_at) → id ASC tie-break; two
88
+ # workspaces plus NULL-workspace rows cover all three scoping answers (no scoping
89
+ # / empty set / a specific workspace). The v11.5.1 tail is in the docgen corpus.
88
90
  NODES: List[Tuple[str, str, str, str, Dict[str, Any], Optional[str], str]] = [
89
91
  ("dec:fusion-alpha", "Decision", "Hybrid retrieval fusion stays alpha weighted",
90
92
  "We decided the ranking keeps alpha fusion: lexical rank plus max normalized vector score.",
@@ -186,14 +188,14 @@ NODES: List[Tuple[str, str, str, str, Dict[str, Any], Optional[str], str]] = [
186
188
  ("legacy:global-note", "Document", "Legacy global note",
187
189
  "A legacy row with no workspace, visible only with include_legacy_global.",
188
190
  {}, None, "2026-06-20T10:00:00"),
191
+ *docgen.DOCGEN_NODES,
189
192
  ]
190
193
 
191
194
  # (chunk_id, parent_node_id, index, node_title, text, chunk_fields, workspace, updated_at)
192
195
  #
193
196
  # The shape mirrors ``KnowledgeGraphIngestMixin``: every chunk is BOTH a ``Chunk``
194
- # node (lexical lane + workspace scoping) and a ``chunks`` row with its own
195
- # embedding (vector lane, rolled up to its parent). Getting that duality wrong is
196
- # the whole reason chunk-heavy queries are in the query set.
197
+ # node (lexical lane + workspace scoping) and a ``chunks`` row with its own embedding
198
+ # (vector lane, rolled up to its parent) — the duality chunk-heavy queries pin.
197
199
  CHUNKS: List[Tuple[str, str, int, str, str, Dict[str, Any], Optional[str], str]] = [
198
200
  ("chunk:handbook:1", "doc:handbook", 0, "handbook.pdf chunk 1",
199
201
  "온보딩 체크리스트: 첫째, 폴더를 연결합니다. 둘째, 질문을 합니다. 셋째, 근거를 확인합니다.",
@@ -218,10 +220,10 @@ CHUNKS: List[Tuple[str, str, int, str, str, Dict[str, Any], Optional[str], str]]
218
220
  # orders by ``weight DESC, created_at DESC, id ASC`` and ``traverse`` caps every BFS
219
221
  # round with ``ORDER BY weight DESC, id ASC``, so an edge set sharing one weight and
220
222
  # one clock proves nothing. Three shapes are deliberate — a weight tie broken by
221
- # ``created_at`` (the ``org:lattice`` pair), a weight *and* clock tie broken by edge
222
- # id (``topic:quality``/``deck:review``), and legacy-global endpoints for scoping.
223
- # Types are canonicalized by the write door (``relates_to``/``owns`` → ``MENTIONS``),
224
- # which is why the goldens record uppercase names the fixture never spells.
223
+ # ``created_at`` (``org:lattice``), a weight *and* clock tie broken by edge id
224
+ # (``topic:quality``/``deck:review``), and legacy-global endpoints for scoping. The
225
+ # write door canonicalizes types (``relates_to``/``owns`` → ``MENTIONS``), which is
226
+ # why the goldens record uppercase names the fixture never spells.
225
227
  EDGES: List[Tuple[str, str, str, float, str]] = [
226
228
  ("dec:fusion-alpha", "concept:retrieval", "mentions", 0.9, "2026-07-01T00:00:00"),
227
229
  ("dec:fusion-alpha", "concept:ranking", "mentions", 0.8, "2026-07-02T00:00:00"),
@@ -246,6 +248,7 @@ EDGES: List[Tuple[str, str, str, float, str]] = [
246
248
  ("task:onboarding-checklist", "page:onboarding", "relates_to", 0.15, "2026-05-01T00:00:00"),
247
249
  ("doc:handbook", "doc:retrieval-spec", "relates_to", 0.1, "2026-05-02T00:00:00"),
248
250
  ("tie:a", "tie:b", "relates_to", 1.0, "2026-07-03T00:00:00"),
251
+ *docgen.DOCGEN_EDGES,
249
252
  ]
250
253
 
251
254
  # ── the conversation corpus (episodic memory, same database file) ────────────
@@ -257,7 +260,7 @@ EDGES: List[Tuple[str, str, str, float, str]] = [
257
260
  # ``conversation_id`` (the ``legacy-previous-history`` bucket), a whitespace-only
258
261
  # first message (the ``새 대화`` placeholder and its later upgrade), an
259
262
  # assistant-first conversation (no upgrade), an empty timestamp (the ``or ""``
260
- # fallbacks), extra keys ``metadata_json`` merges flat, and ko/en content.
263
+ # fallbacks), extra keys ``metadata_json`` merges flat, ko/en content.
261
264
  MESSAGES: List[Tuple[Any, ...]] = [
262
265
  ("conv-a", "user", "온보딩 체크리스트 어떻게 시작해?", "jiwon@lattice.ai", "지원", "web", "2026-07-20T09:00:00", WS_ALPHA, "org-1", {}),
263
266
  ("conv-a", "assistant", "먼저 폴더를 연결하세요. 그다음 질문하면 됩니다.", "jiwon@lattice.ai", None, "web", "2026-07-20T09:00:05", WS_ALPHA, "org-1", {}),
@@ -307,17 +310,15 @@ QUERIES: List[Dict[str, Any]] = [
307
310
  {"key": "ws_alpha", "query": "hybrid retrieval ranking", "allowed": [WS_ALPHA]},
308
311
  {"key": "ws_alpha_legacy", "query": "회의 결정 사항", "allowed": [WS_ALPHA], "legacy": True},
309
312
  {"key": "ws_beta", "query": "온보딩 체크리스트", "allowed": [WS_BETA]},
310
- # A vector floor nothing clears — the only way to reach the stale-embedder
311
- # probe and the lexical-only fusion label without breaking the store.
313
+ # A vector floor nothing clears: the stale-embedder probe and the
314
+ # lexical-only fusion label, without breaking the store.
312
315
  {"key": "min_vector_floor", "query": "hybrid retrieval ranking", "min_vector": 0.95},
313
316
  # top_k small enough that the rerank window (top_k * 2) cuts the candidates.
314
317
  {"key": "top_k_small", "query": "hybrid retrieval ranking", "top_k": 3},
315
- # Pinned alpha: no policy, so no class, no rewrite, and no age decay on a
316
- # query that would otherwise be recency-classed.
318
+ # Pinned alpha: no policy, so no class/rewrite/age decay on a recency query.
317
319
  {"key": "alpha_pinned", "query": "지난주 회의 기록", "alpha": 0.2},
318
- # A limit below the FTS hit count, so `ORDER BY rank LIMIT ?` decides which
319
- # rows exist at all — the one place bm25 ordering (and therefore a SQLite
320
- # version difference between the two runtimes) is observable.
320
+ # A limit below the FTS hit count, so `ORDER BY rank LIMIT ?` decides which rows
321
+ # exist at all — the one place bm25 ordering (a SQLite version difference) shows.
321
322
  {"key": "fts_rank_cut", "query": "Tie candidate", "limit": 2, "top_k": 2},
322
323
  ]
323
324
 
@@ -403,8 +404,8 @@ SUITES: Dict[str, List[Dict[str, Any]]] = {
403
404
  {"key": "en_code", "query": "vector_search() returns"},
404
405
  {"key": "ko_person", "query": "담당자 누구"},
405
406
  {"key": "en_filler", "query": " what is the retrieval specification please "},
406
- # Explicit weights disable BOTH the rewrite and the age decay, on a
407
- # query that would otherwise get both. That asymmetry is the contract.
407
+ # Explicit weights disable BOTH rewrite and age decay on a query that
408
+ # would otherwise get both — that asymmetry is the contract.
408
409
  {"key": "pinned_recency", "query": "지난주 회의 기록", "weights": {"keyword": 0.5, "vector": 0.3, "graph": 0.2}},
409
410
  {"key": "pinned_partial", "query": "hybrid retrieval ranking", "weights": {"graph": 1.0}},
410
411
  {"key": "pinned_zero", "query": "회의 결정 사항", "weights": {"keyword": 0.0, "vector": 0.0, "graph": 0.0}},
@@ -476,6 +477,8 @@ SUITES: Dict[str, List[Dict[str, Any]]] = {
476
477
  {"key": "budget_zero", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "notes": "노트", "budget": 0},
477
478
  {"key": "knowledge_limit_one", "query": "hybrid retrieval ranking", "knowledge_limit": 1},
478
479
  ],
480
+ # v11.5.1: the document-generation ports, specs and runners alike.
481
+ **docgen.DOCGEN_SUITES,
479
482
  }
480
483
 
481
484
  #: Texts whose tokenizer output, hashes and vectors pin the embedding port.
@@ -517,10 +520,12 @@ def pinned_environment() -> Iterator[None]:
517
520
  def frozen_clock() -> Iterator[None]:
518
521
  """Freeze every ported ``datetime.now()`` at :data:`FROZEN_NOW`.
519
522
 
520
- Only the recency-class age decay reads the clock, through the ``datetime``
521
- name its module imported — so rebinding that name is the whole patch. Two
522
- modules do it: the graph-layer and the service-layer ``hybrid_search``.
523
+ Only the recency decay reads the clock, through the ``datetime`` name its
524
+ module imported, so rebinding that name is the whole patch. Three modules
525
+ reach it: the graph-layer and service-layer ``hybrid_search``, and the
526
+ document-generation search's own 14-day half-life.
523
527
  """
528
+ from lattice_brain.graph import retrieval_docgen as docgen_module
524
529
  from lattice_brain.graph.retrieval import hybrid as hybrid_module
525
530
  from latticeai.services import search_service as service_module
526
531
 
@@ -531,7 +536,7 @@ def frozen_clock() -> Iterator[None]:
531
536
  def now(cls, tz=None): # noqa: ARG003 — mirrors datetime.now's signature
532
537
  return frozen
533
538
 
534
- modules = (hybrid_module, service_module)
539
+ modules = (hybrid_module, service_module, docgen_module)
535
540
  originals = [module.datetime for module in modules]
536
541
  for module in modules:
537
542
  module.datetime = _FrozenDatetime
@@ -546,8 +551,8 @@ def frozen_clock() -> Iterator[None]:
546
551
  def rules_only_extraction() -> Iterator[None]:
547
552
  """Force ``_topic_candidates`` down its rule-based path.
548
553
 
549
- The LLM path needs a bound router no fixture run has, but "no router happens
550
- to be bound" is an accident and this contract cannot rest on one.
554
+ The LLM path needs a bound router no fixture run has, but "no router
555
+ happens to be bound" is an accident this contract cannot rest on.
551
556
  """
552
557
  from lattice_brain.graph._kg_common import extraction
553
558
 
@@ -618,13 +623,12 @@ def build_store(db_path: Path) -> None:
618
623
  for from_node, to_node, edge_type, weight, stamp in EDGES:
619
624
  # ``_upsert_edge`` stamps ``created_at`` from the wall clock in BOTH
620
625
  # tables, and the read path is the ``kgv2_edges`` view over ``edges_v2``:
621
- # backdating only the legacy table (as this generator first did) left
622
- # the relationship ordering moving with the clock.
626
+ # backdating only the legacy table left the ordering moving with the clock.
623
627
  edge_id = store._upsert_edge(conn, from_node, to_node, edge_type, weight, {})
624
628
  conn.execute("UPDATE edges SET created_at=? WHERE id=?", (stamp, edge_id))
625
629
  conn.execute("UPDATE edges_v2 SET created_at=? WHERE id=?", (stamp, edge_id))
626
- # ``indexed_at`` decides the candidate scan order (and, when the cap bites,
627
- # which candidates exist at all), so it is assigned rather than inherited.
630
+ # ``indexed_at`` decides the candidate scan order (and, when the cap
631
+ # bites, which candidates exist), so it is assigned rather than inherited.
628
632
  item_ids = [row["item_id"] for row in conn.execute(
629
633
  "SELECT item_id FROM vector_embeddings ORDER BY item_id ASC"
630
634
  ).fetchall()]
@@ -636,8 +640,8 @@ def build_store(db_path: Path) -> None:
636
640
  store.record_embedder_fingerprint()
637
641
  write_conversations(db_path)
638
642
 
639
- # Leave one self-contained file behind: checkpoint the WAL, drop back to a
640
- # rollback journal so no sidecar is committed, and compact.
643
+ # One self-contained file: checkpoint the WAL, drop to a rollback journal
644
+ # so no sidecar is committed, and compact.
641
645
  with sqlite3.connect(str(db_path)) as conn:
642
646
  conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
643
647
  conn.execute("PRAGMA journal_mode=DELETE")
@@ -678,18 +682,18 @@ def run_engine(store, engine: str, spec: Dict[str, Any]) -> Dict[str, Any]:
678
682
  return ENGINES[engine](store, spec)
679
683
 
680
684
 
681
- # ── suites (v11.5.0): KG reads, the service layer, history, context ──────────
685
+ # ── the suites beyond search (v11.5.0/v11.5.1) ───────────────────────────────
682
686
  #
683
687
  # The Phase-1 engines share one query shape (a query set × an engine set); the
684
- # Phase-2/3 ports do not, so each gets its own spec list and runner, both carried
685
- # in the manifest so Rust and Python enumerate exactly the same work.
688
+ # later ports do not, so each gets its own spec list and runner, both carried in
689
+ # the manifest so Rust and Python enumerate exactly the same work.
686
690
 
687
691
 
688
692
  class Harness:
689
693
  """Every Python entry point the v11.5.0 goldens are produced from.
690
694
 
691
695
  ``require_auth=False`` is the loopback-owner configuration the native routes
692
- reproduce: the history scope is whatever the caller passes explicitly.
696
+ reproduce: the history scope is whatever the caller passes.
693
697
  """
694
698
 
695
699
  def __init__(self, db_path: Path):
@@ -718,8 +722,7 @@ def _history_scope(spec: Dict[str, Any]) -> Dict[str, Any]:
718
722
  """The identity/workspace scope every history read takes.
719
723
 
720
724
  ``include_legacy_global`` defaults to ``True`` — ``ConversationStore``'s own
721
- default, the opposite of the graph layer's and the kind of asymmetry a port
722
- gets wrong.
725
+ default, the opposite of the graph layer's and an asymmetry a port gets wrong.
723
726
  """
724
727
  allowed = spec.get("allowed")
725
728
  return {
@@ -801,7 +804,7 @@ def _context_seams(h: Harness, spec: Dict[str, Any]) -> Dict[str, Any]:
801
804
 
802
805
  ``memories`` / ``artifacts`` / ``notes`` are *data* seams: the payload is the
803
806
  spec, so both runtimes feed the assembler the same bytes and what is under
804
- test is the assembler. ``knowledge`` and ``recent`` are real — the
807
+ test is the assembler. ``knowledge`` and ``recent`` are real engines — the
805
808
  service-layer hybrid search and the durable history reader.
806
809
  """
807
810
  from latticeai.api.chat_helpers import build_recent_chat_context
@@ -868,6 +871,7 @@ SUITE_RUNNERS: Dict[str, Callable[[Harness, Dict[str, Any]], Any]] = {
868
871
  "conversation_messages": _run_conversation_messages,
869
872
  "history_search": _run_history_search,
870
873
  "context_assemble": _run_context_assemble,
874
+ **docgen.DOCGEN_RUNNERS,
871
875
  }
872
876
 
873
877
 
@@ -901,7 +905,7 @@ def suite_payload(suite: str, spec: Dict[str, Any], result: Any) -> Dict[str, An
901
905
  """One suite golden: the spec that produced it, verbatim, and the answer.
902
906
 
903
907
  The spec rides along rather than being flattened into a fixed ``params``
904
- block: these entry points share no parameter shape.
908
+ block — these entry points share no parameter shape.
905
909
  """
906
910
  return {"suite": suite, "key": spec["key"], "spec": spec, "result": result}
907
911
 
@@ -982,8 +986,7 @@ def main() -> int:
982
986
  _dump(GOLDEN_DIR / "embeddings_golden.json", embeddings_golden())
983
987
  _dump(GOLDEN_DIR / "rounding_golden.json", rounding_golden())
984
988
  _dump(GOLDEN_DIR / "manifest.json", manifest())
985
- # ``KnowledgeGraphStore.__init__`` creates its blob directory eagerly and the
986
- # fixture has no blobs; an empty directory beside an artefact is just noise.
989
+ # The store creates its blob directory eagerly and the fixture has no blobs.
987
990
  blob_dir = STORE_PATH.parent / "blobs"
988
991
  if blob_dir.is_dir() and not any(blob_dir.iterdir()):
989
992
  blob_dir.rmdir()