deeptutor-memory 1.7.0__py3-none-any.whl
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.
- deeptutor/agents/loop/native_memory.py +24 -0
- deeptutor/api/routers/memory.py +785 -0
- deeptutor/services/memory/__init__.py +42 -0
- deeptutor/services/memory/consolidator/__init__.py +62 -0
- deeptutor/services/memory/consolidator/chunker.py +131 -0
- deeptutor/services/memory/consolidator/guards.py +104 -0
- deeptutor/services/memory/consolidator/line_doc.py +486 -0
- deeptutor/services/memory/consolidator/meta.py +194 -0
- deeptutor/services/memory/consolidator/modes/__init__.py +31 -0
- deeptutor/services/memory/consolidator/modes/_runtime.py +319 -0
- deeptutor/services/memory/consolidator/modes/_shims.py +129 -0
- deeptutor/services/memory/consolidator/modes/audit.py +481 -0
- deeptutor/services/memory/consolidator/modes/dedup.py +228 -0
- deeptutor/services/memory/consolidator/modes/merge.py +227 -0
- deeptutor/services/memory/consolidator/modes/update.py +706 -0
- deeptutor/services/memory/consolidator/parse.py +135 -0
- deeptutor/services/memory/consolidator/prompts/en/_meta.yaml +36 -0
- deeptutor/services/memory/consolidator/prompts/en/audit_l2.yaml +42 -0
- deeptutor/services/memory/consolidator/prompts/en/audit_l3.yaml +38 -0
- deeptutor/services/memory/consolidator/prompts/en/dedup.yaml +37 -0
- deeptutor/services/memory/consolidator/prompts/en/update_l2.yaml +45 -0
- deeptutor/services/memory/consolidator/prompts/en/update_l3.yaml +51 -0
- deeptutor/services/memory/consolidator/prompts/zh/_meta.yaml +35 -0
- deeptutor/services/memory/consolidator/prompts/zh/audit_l2.yaml +40 -0
- deeptutor/services/memory/consolidator/prompts/zh/audit_l3.yaml +34 -0
- deeptutor/services/memory/consolidator/prompts/zh/dedup.yaml +34 -0
- deeptutor/services/memory/consolidator/prompts/zh/update_l2.yaml +40 -0
- deeptutor/services/memory/consolidator/prompts/zh/update_l3.yaml +43 -0
- deeptutor/services/memory/consolidator/references.py +388 -0
- deeptutor/services/memory/consolidator/runs.py +405 -0
- deeptutor/services/memory/document.py +235 -0
- deeptutor/services/memory/ids.py +80 -0
- deeptutor/services/memory/ops.py +149 -0
- deeptutor/services/memory/paths.py +104 -0
- deeptutor/services/memory/prompts/en.yaml +156 -0
- deeptutor/services/memory/prompts/zh.yaml +144 -0
- deeptutor/services/memory/recall.py +237 -0
- deeptutor/services/memory/settings.py +198 -0
- deeptutor/services/memory/snapshot/__init__.py +127 -0
- deeptutor/services/memory/snapshot/adapters.py +690 -0
- deeptutor/services/memory/snapshot/diff.py +67 -0
- deeptutor/services/memory/snapshot/entity.py +92 -0
- deeptutor/services/memory/snapshot/store.py +113 -0
- deeptutor/services/memory/store.py +453 -0
- deeptutor/services/memory/trace.py +153 -0
- deeptutor_cli/memory.py +99 -0
- deeptutor_memory-1.7.0.dist-info/METADATA +6 -0
- deeptutor_memory-1.7.0.dist-info/RECORD +50 -0
- deeptutor_memory-1.7.0.dist-info/WHEEL +5 -0
- deeptutor_memory-1.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Reference validation + raw-trace lookup used by update / audit.
|
|
2
|
+
|
|
3
|
+
Two distinct concerns share this module because they both center on
|
|
4
|
+
"the set of refs the LLM is allowed to cite":
|
|
5
|
+
|
|
6
|
+
* **Update mode** — refs must point at entities that appear in the
|
|
7
|
+
current chunk's source range. :func:`refs_in_chunk` returns the
|
|
8
|
+
allowed pool; :func:`validate_fact_refs` filters extracted facts.
|
|
9
|
+
* **Audit mode** — every entry on a md chunk gets its raw-trace
|
|
10
|
+
content spliced in as evidence. :func:`annotate_line_with_evidence`
|
|
11
|
+
formats one entry + sources into a block fed to the LLM.
|
|
12
|
+
|
|
13
|
+
No I/O happens beyond reading from the same in-memory entity / L2 doc
|
|
14
|
+
maps the caller has already loaded — the modes are responsible for
|
|
15
|
+
hydrating those once per run.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
import logging
|
|
22
|
+
import re
|
|
23
|
+
from typing import Iterable
|
|
24
|
+
|
|
25
|
+
from deeptutor.services.memory.document import Document, Entry
|
|
26
|
+
from deeptutor.services.memory.ids import is_entry_id, is_valid_ref
|
|
27
|
+
from deeptutor.services.memory.snapshot.entity import Entity
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── Update-mode helpers ─────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class ExtractedFact:
|
|
37
|
+
"""One fact pulled by the LLM during update mode."""
|
|
38
|
+
|
|
39
|
+
text: str
|
|
40
|
+
refs: list[str]
|
|
41
|
+
section: str = ""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def refs_in_chunk_l2(
|
|
45
|
+
entities: Iterable[Entity],
|
|
46
|
+
*,
|
|
47
|
+
surface: str,
|
|
48
|
+
chunk_text: str,
|
|
49
|
+
) -> set[str]:
|
|
50
|
+
"""Set of allowed refs (``surface:entity_id``) for this chunk.
|
|
51
|
+
|
|
52
|
+
An entity is considered "in this chunk" if its rendered marker
|
|
53
|
+
appears in ``chunk_text``. The marker is the same one written by
|
|
54
|
+
:func:`render_traces_for_concat`.
|
|
55
|
+
"""
|
|
56
|
+
allowed: set[str] = set()
|
|
57
|
+
for ent in entities:
|
|
58
|
+
marker = _entity_marker(surface, ent.id)
|
|
59
|
+
if marker in chunk_text:
|
|
60
|
+
allowed.add(f"{surface}:{ent.id}")
|
|
61
|
+
return allowed
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def refs_in_span_l2(
|
|
65
|
+
entities: Iterable[Entity],
|
|
66
|
+
*,
|
|
67
|
+
surface: str,
|
|
68
|
+
full_text: str,
|
|
69
|
+
start: int,
|
|
70
|
+
end: int,
|
|
71
|
+
) -> set[str]:
|
|
72
|
+
"""Allowed L2 refs for a chunk span, including long split entities."""
|
|
73
|
+
markers: list[tuple[int, str]] = []
|
|
74
|
+
for ent in entities:
|
|
75
|
+
marker = _entity_marker(surface, ent.id)
|
|
76
|
+
pos = full_text.find(marker)
|
|
77
|
+
if pos != -1:
|
|
78
|
+
markers.append((pos, f"{surface}:{ent.id}"))
|
|
79
|
+
return _refs_overlapping_span(markers, text_len=len(full_text), start=start, end=end)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_L3_SURFACE_HEADER_RE = re.compile(r"^### surface: ([a-z][a-z0-9_-]*)", re.MULTILINE)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def refs_in_chunk_l3(
|
|
86
|
+
chunk_text: str,
|
|
87
|
+
*,
|
|
88
|
+
entries_by_surface: dict[str, list[Entry]],
|
|
89
|
+
) -> set[str]:
|
|
90
|
+
"""L3 refs are *surface names* — pointers to the L2 md the synthesis
|
|
91
|
+
drew from. The render emits one ``### surface: <name>`` header per
|
|
92
|
+
surface block; we collect every header visible in the chunk text.
|
|
93
|
+
"""
|
|
94
|
+
del entries_by_surface # surface list is derived from the rendered text
|
|
95
|
+
return {m.group(1) for m in _L3_SURFACE_HEADER_RE.finditer(chunk_text)}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def refs_in_span_l3(
|
|
99
|
+
*,
|
|
100
|
+
entries_by_surface: dict[str, list[Entry]],
|
|
101
|
+
full_text: str,
|
|
102
|
+
start: int,
|
|
103
|
+
end: int,
|
|
104
|
+
) -> set[str]:
|
|
105
|
+
"""Surface refs whose render block intersects ``[start, end)``.
|
|
106
|
+
|
|
107
|
+
A surface block runs from its ``### surface:`` header to the next
|
|
108
|
+
one (or the end of the doc). A chunk may legitimately start
|
|
109
|
+
mid-block thanks to the overlap window, so we keep any surface
|
|
110
|
+
whose block extends into the chunk window.
|
|
111
|
+
"""
|
|
112
|
+
del entries_by_surface
|
|
113
|
+
headers = list(_L3_SURFACE_HEADER_RE.finditer(full_text))
|
|
114
|
+
if not headers:
|
|
115
|
+
return set()
|
|
116
|
+
allowed: set[str] = set()
|
|
117
|
+
for idx, match in enumerate(headers):
|
|
118
|
+
block_start = match.start()
|
|
119
|
+
block_end = headers[idx + 1].start() if idx + 1 < len(headers) else len(full_text)
|
|
120
|
+
if block_start < end and block_end > start:
|
|
121
|
+
allowed.add(match.group(1))
|
|
122
|
+
return allowed
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def validate_fact_refs(
|
|
126
|
+
fact: ExtractedFact,
|
|
127
|
+
*,
|
|
128
|
+
allowed: set[str],
|
|
129
|
+
enforce_required: bool,
|
|
130
|
+
drop_invalid: bool,
|
|
131
|
+
) -> tuple[list[str], str | None]:
|
|
132
|
+
"""Filter / reject a fact's refs.
|
|
133
|
+
|
|
134
|
+
Returns ``(kept_refs, reject_reason)``. ``reject_reason`` is ``None``
|
|
135
|
+
when the fact survives. Behavior:
|
|
136
|
+
|
|
137
|
+
* ``enforce_required=True`` + no refs → reject.
|
|
138
|
+
* ``drop_invalid=True``: refs outside ``allowed`` are removed;
|
|
139
|
+
if the result is empty under ``enforce_required`` → reject.
|
|
140
|
+
* ``drop_invalid=False``: any out-of-pool ref → reject the fact.
|
|
141
|
+
"""
|
|
142
|
+
if not fact.refs:
|
|
143
|
+
if enforce_required:
|
|
144
|
+
return [], "missing refs"
|
|
145
|
+
return [], None
|
|
146
|
+
|
|
147
|
+
if drop_invalid:
|
|
148
|
+
kept = [
|
|
149
|
+
normalized
|
|
150
|
+
for ref in fact.refs
|
|
151
|
+
if (normalized := _normalize_allowed_ref(ref, allowed)) is not None
|
|
152
|
+
]
|
|
153
|
+
if not kept and enforce_required:
|
|
154
|
+
return [], "no surviving refs in chunk pool"
|
|
155
|
+
return _dedupe(kept), None
|
|
156
|
+
|
|
157
|
+
for ref in fact.refs:
|
|
158
|
+
normalized = _normalize_allowed_ref(ref, allowed)
|
|
159
|
+
if normalized is None and not is_valid_ref(ref):
|
|
160
|
+
return [], f"malformed ref {ref!r}"
|
|
161
|
+
if normalized is None:
|
|
162
|
+
return [], f"out-of-pool ref {ref!r}"
|
|
163
|
+
return _dedupe([_normalize_allowed_ref(ref, allowed) or ref for ref in fact.refs]), None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ── Rendering: traces → concatenated text ───────────────────────────────
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
_ENTITY_HEADER_FMT = "=== {marker} ==="
|
|
170
|
+
# ``_L2_ENTRY_HEADER_FMT`` and ``_l2_entry_marker`` are no longer used:
|
|
171
|
+
# the L3 input is text-only, so no L2-entry markers are emitted. They
|
|
172
|
+
# would have been ``"=== @l2 m_xxx ==="``.
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def render_traces_for_concat(entities: list[Entity], *, surface: str) -> str:
|
|
176
|
+
"""Concatenate a list of L2 raw-trace entities into one timeline string.
|
|
177
|
+
|
|
178
|
+
The chunk-pool detector relies on the marker line being unique per
|
|
179
|
+
entity, so it doubles as both a human delimiter and a machine anchor.
|
|
180
|
+
"""
|
|
181
|
+
blocks: list[str] = []
|
|
182
|
+
for ent in entities:
|
|
183
|
+
header = _ENTITY_HEADER_FMT.format(marker=_entity_marker(surface, ent.id))
|
|
184
|
+
meta_str = _format_meta(ent)
|
|
185
|
+
body = (ent.content or "").strip()
|
|
186
|
+
block = "\n".join(
|
|
187
|
+
x
|
|
188
|
+
for x in (
|
|
189
|
+
header,
|
|
190
|
+
f"ref: {surface}:{ent.id}",
|
|
191
|
+
f"label: {ent.label}",
|
|
192
|
+
f"ts: {ent.ts or '?'}",
|
|
193
|
+
f"meta: {meta_str}" if meta_str else None,
|
|
194
|
+
"",
|
|
195
|
+
body,
|
|
196
|
+
)
|
|
197
|
+
if x is not None
|
|
198
|
+
)
|
|
199
|
+
blocks.append(block)
|
|
200
|
+
return "\n\n".join(blocks)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def render_l2_entries_for_concat(
|
|
204
|
+
entries_by_surface: dict[str, list[Entry]],
|
|
205
|
+
) -> str:
|
|
206
|
+
"""Concatenate L2 entries (per surface) into one text for L3 chunking.
|
|
207
|
+
|
|
208
|
+
L3 is a *text-only* synthesis layer: the user has explicitly said the
|
|
209
|
+
LLM should not see — or copy — L2 footnote provenance. So this render
|
|
210
|
+
emits **only** the surface header + each entry's prose. No entry-id
|
|
211
|
+
markers, no ``ref:`` / ``refs:`` lines. As a result the chunk-pool
|
|
212
|
+
detector for L3 always returns an empty set (see
|
|
213
|
+
:func:`refs_in_span_l3`); L3 facts have no refs.
|
|
214
|
+
"""
|
|
215
|
+
blocks: list[str] = []
|
|
216
|
+
for surface, entries in entries_by_surface.items():
|
|
217
|
+
if not entries:
|
|
218
|
+
continue
|
|
219
|
+
blocks.append(f"### surface: {surface}")
|
|
220
|
+
for entry in entries:
|
|
221
|
+
# Section is kept (it shapes synthesis) but emitted as a
|
|
222
|
+
# parenthetical tag rather than a structured field, so the
|
|
223
|
+
# model treats it as context, not a citation hook.
|
|
224
|
+
tag = f"[{entry.section}] " if entry.section else ""
|
|
225
|
+
blocks.append(f"- {tag}{entry.text}")
|
|
226
|
+
return "\n\n".join(blocks)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ── Audit-mode helpers ──────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def annotate_l2_line_with_evidence(
|
|
233
|
+
line_number: int,
|
|
234
|
+
entry: Entry,
|
|
235
|
+
*,
|
|
236
|
+
surface: str,
|
|
237
|
+
entity_lookup: dict[str, Entity],
|
|
238
|
+
) -> str:
|
|
239
|
+
"""Render one L2 bullet + every raw trace it cites, full content.
|
|
240
|
+
|
|
241
|
+
Output is intentionally human-readable so the model can reason
|
|
242
|
+
about correspondence (md statement ↔ original wording). No
|
|
243
|
+
truncation, ever — that is the point of audit mode.
|
|
244
|
+
"""
|
|
245
|
+
lines: list[str] = [
|
|
246
|
+
f"line {line_number}: {entry.text} [^{entry.id}]",
|
|
247
|
+
f" section: {entry.section}",
|
|
248
|
+
]
|
|
249
|
+
if not entry.refs:
|
|
250
|
+
lines.append(" sources: (none)")
|
|
251
|
+
return "\n".join(lines)
|
|
252
|
+
lines.append(f" sources ({len(entry.refs)}):")
|
|
253
|
+
for ref in entry.refs:
|
|
254
|
+
if ":" not in ref:
|
|
255
|
+
lines.append(f" └ {ref}: (malformed)")
|
|
256
|
+
continue
|
|
257
|
+
_, ent_id = ref.split(":", 1)
|
|
258
|
+
ent = entity_lookup.get(ent_id)
|
|
259
|
+
if ent is None:
|
|
260
|
+
lines.append(f" └ {ref}: (entity not found in current workspace)")
|
|
261
|
+
continue
|
|
262
|
+
body = (ent.content or "").rstrip()
|
|
263
|
+
lines.append(f" └ {ref} (ts={ent.ts or '?'}, label={ent.label!r}):")
|
|
264
|
+
for src_line in body.splitlines():
|
|
265
|
+
lines.append(f" {src_line}")
|
|
266
|
+
return "\n".join(lines)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def annotate_l3_line_with_evidence(
|
|
270
|
+
line_number: int,
|
|
271
|
+
entry: Entry,
|
|
272
|
+
*,
|
|
273
|
+
l2_entry_lookup: dict[str, Entry],
|
|
274
|
+
) -> str:
|
|
275
|
+
"""Render one L3 bullet + every L2 entry it cites, full text + refs."""
|
|
276
|
+
lines: list[str] = [
|
|
277
|
+
f"line {line_number}: {entry.text} [^{entry.id}]",
|
|
278
|
+
f" section: {entry.section}",
|
|
279
|
+
]
|
|
280
|
+
if not entry.refs:
|
|
281
|
+
lines.append(" sources: (none)")
|
|
282
|
+
return "\n".join(lines)
|
|
283
|
+
lines.append(f" sources ({len(entry.refs)}):")
|
|
284
|
+
for ref in entry.refs:
|
|
285
|
+
if not is_entry_id(ref):
|
|
286
|
+
lines.append(f" └ {ref}: (malformed L2 id)")
|
|
287
|
+
continue
|
|
288
|
+
src = l2_entry_lookup.get(ref)
|
|
289
|
+
if src is None:
|
|
290
|
+
lines.append(f" └ {ref}: (L2 entry not found)")
|
|
291
|
+
continue
|
|
292
|
+
lines.append(f" └ {ref} (section={src.section!r}):")
|
|
293
|
+
lines.append(f" {src.text}")
|
|
294
|
+
if src.refs:
|
|
295
|
+
lines.append(f" upstream refs: {', '.join(src.refs)}")
|
|
296
|
+
return "\n".join(lines)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ── Internals ───────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _entity_marker(surface: str, entity_id: str) -> str:
|
|
303
|
+
return f"@entity {surface}:{entity_id}"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _format_meta(ent: Entity) -> str:
|
|
307
|
+
if not ent.metadata:
|
|
308
|
+
return ""
|
|
309
|
+
bits = [f"{k}={v}" for k, v in ent.metadata.items() if v not in (None, "", [], {})]
|
|
310
|
+
return " ".join(bits)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _normalize_allowed_ref(ref: str, allowed: set[str]) -> str | None:
|
|
314
|
+
"""Return the canonical allowed ref when the model added label text.
|
|
315
|
+
|
|
316
|
+
LLMs often copy a rendered source as ``<label>:chat:<id>`` even though
|
|
317
|
+
the prompt asks for ``chat:<id>``. Treat that as a recoverable citation
|
|
318
|
+
as long as it unambiguously ends with an allowed chunk-local ref.
|
|
319
|
+
"""
|
|
320
|
+
candidate = _strip_ref_wrappers(str(ref).strip())
|
|
321
|
+
if candidate in allowed and is_valid_ref(candidate):
|
|
322
|
+
return candidate
|
|
323
|
+
for allowed_ref in sorted(allowed, key=len, reverse=True):
|
|
324
|
+
if not is_valid_ref(allowed_ref):
|
|
325
|
+
continue
|
|
326
|
+
if _has_ref_suffix(candidate, allowed_ref):
|
|
327
|
+
return allowed_ref
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _strip_ref_wrappers(ref: str) -> str:
|
|
332
|
+
return ref.strip().strip("`[](){}<>").lstrip("^").strip()
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _has_ref_suffix(candidate: str, allowed_ref: str) -> bool:
|
|
336
|
+
if candidate == allowed_ref:
|
|
337
|
+
return True
|
|
338
|
+
if not candidate.endswith(allowed_ref):
|
|
339
|
+
return False
|
|
340
|
+
prefix = candidate[: -len(allowed_ref)]
|
|
341
|
+
if not prefix:
|
|
342
|
+
return True
|
|
343
|
+
# Common hallucinated forms: "Title:chat:id", "Title?chat:id",
|
|
344
|
+
# "[^m_id]". Do not accept alnum/underscore adjacency.
|
|
345
|
+
return prefix[-1] in {":", ":", "?", "?", "#", "/", "|", " ", "\t", "\n", "^"}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _dedupe(refs: list[str]) -> list[str]:
|
|
349
|
+
seen: set[str] = set()
|
|
350
|
+
out: list[str] = []
|
|
351
|
+
for ref in refs:
|
|
352
|
+
if ref in seen:
|
|
353
|
+
continue
|
|
354
|
+
seen.add(ref)
|
|
355
|
+
out.append(ref)
|
|
356
|
+
return out
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _refs_overlapping_span(
|
|
360
|
+
markers: list[tuple[int, str]], *, text_len: int, start: int, end: int
|
|
361
|
+
) -> set[str]:
|
|
362
|
+
allowed: set[str] = set()
|
|
363
|
+
ordered = sorted(markers, key=lambda item: item[0])
|
|
364
|
+
for idx, (block_start, ref) in enumerate(ordered):
|
|
365
|
+
block_end = ordered[idx + 1][0] if idx + 1 < len(ordered) else text_len
|
|
366
|
+
if block_start < end and block_end > start:
|
|
367
|
+
allowed.add(ref)
|
|
368
|
+
return allowed
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def collect_l2_entries(docs: dict[str, Document]) -> dict[str, list[Entry]]:
|
|
372
|
+
"""Helper for L3 — pull all entries from a {surface: Document} map."""
|
|
373
|
+
return {surface: doc.all_entries() for surface, doc in docs.items()}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
__all__ = [
|
|
377
|
+
"ExtractedFact",
|
|
378
|
+
"annotate_l2_line_with_evidence",
|
|
379
|
+
"annotate_l3_line_with_evidence",
|
|
380
|
+
"collect_l2_entries",
|
|
381
|
+
"refs_in_chunk_l2",
|
|
382
|
+
"refs_in_chunk_l3",
|
|
383
|
+
"refs_in_span_l2",
|
|
384
|
+
"refs_in_span_l3",
|
|
385
|
+
"render_l2_entries_for_concat",
|
|
386
|
+
"render_traces_for_concat",
|
|
387
|
+
"validate_fact_refs",
|
|
388
|
+
]
|