RFC-0015: chronicle.manifest Dataset Sidecar¶
| Field | Value |
|---|---|
| Status | Proposed |
| Format name | chronicle.manifest(TOML) |
| Date | 2026-09-07(2026-09-11 更新) |
| Component | persisting-pchronicle、pchronicle CLI、Warehouse Datasets UI |
| Implements | crates/persisting-pchronicle/src/store/chronicle_manifest.rs |
| Related | RFC-0014 Compact JSONL · RFC-0013 path Directory · RFC-0001 Storyline |
摘要¶
chronicle.manifest 是放在 Dataset 节点根目录的 pChronicle 自有 TOML sidecar。
它用于廉价发现,并保存常用聚合统计,使 pchronicle ls 和 Web Datasets 不必打开 Lance
或扫描全部记录即可分类目录,并展示类型 / 轨迹数预览。
本文使用 RFC 2119 的 MUST、MUST NOT、SHOULD 与 MAY。
动机¶
大型本地数据集(例如含大量行与 _offload/ 的 compact-jsonl Lance 目录)目前会迫使
discovery 执行 Dataset::open,并迫使 Datasets UI 用 run 摘要反推文件夹。当
Warehouse 挂载多个此类根目录时,约五秒一次的 catalog 刷新与 tree 轮询会让 serve 进程
持续高 CPU,即便用户只在浏览一层目录。
Lance 内部的 _versions/*.manifest 是 Lance 的 MVCC 控制面。pChronicle MUST NOT 在其中
编码应用层发现或 UI 统计。
pChronicle 已对其它布局使用应用控制文件(Storyline 的 CURRENT、Events 的
_manifest.json)。chronicle.manifest 把该模式扩展为可嵌套的 Dataset 节点描述符。
目标与非目标¶
目标:
- 定义稳定的文件名、TOML schema 与嵌套规则;
- 让 discovery 通过读小 TOML 文件即可分类 Dataset 节点;
- 持久化
ls/ Web Datasets 预览所需的聚合统计(类型与轨迹数); - 通过自动扫描子目录中的
chronicle.manifest支持嵌套 Dataset 树; - 把 list 和 open 分开:
list(path)与 shellls一致:只列一层 children,MUST NOT 把嵌套 source 摊平;open(path)生成 Snapshot。叶子 Dataset 是一个 source。纯目录 MAY 被当成 虚拟 dataset 打开:该路径下所有嵌套 Dataset 叶子与外围 JSON 合成一个查询空间。- 在 sidecar 缺失时,仍可用
CURRENT/ compact-jsonl 标记做 Dataset 分类。listMUST NOT 为分类而全量递归列举对象存储前缀。对 Directory 做open时 MAY 在现有max_entries/max_files上限内递归。
非目标(v1):
- 扩展 Lance protobuf manifest;
- 用 sidecar 替代 SQL 或详细 run/record 列表;
- 在父 manifest 中手写显式
children列表; - 把 Storyline
CURRENT或 Events_manifest.json改写成此格式(它们仍是各自布局的权威;可选后续对齐)。
术语¶
- Dataset 节点:作为物理 source 根(leaf)或聚合嵌套 Dataset 节点(branch)的目录。
- Leaf:
format标识物理存储的节点(v1:compact-jsonl/v1或storyline/v1)。 - Branch:用于嵌套子节点、没有物理
format的节点。 - Directory:没有叶子 Dataset 标记的路径。对
list而言是导航文件夹;仍 MAY 被open成虚拟 dataset。这不是 RFC-0013 的 path Directory(名字 → path + ACL)。 - list:对某一 path 的一层列举。CLI
pchronicle ls与 Web Datasets MUST 共用此操作。 - open:把 path 钉成 Snapshot(
query/find/stats/ serve 挂载 / Open in Runs)。 - 虚拟 dataset:
open一个 Directory 得到的 Snapshot:嵌套 Dataset 叶子,加上不在 leaf 内部的 JSON / JSONL / NDJSON。 - Fingerprint:把
[stats]绑定到某一物理修订的字符串,供读者检测过期。
文件位置与名称¶
- 文件名 MUST 恰好为
chronicle.manifest。 - 文件 MUST 位于 Dataset 节点根(与 Lance
data/、StorylineCURRENT等并列)。 - 编码 MUST 为 UTF-8 TOML。
嵌套与发现¶
list 与 open 共用分类规则,MUST NOT 共用结果形态。
路径按以下顺序判定为 叶子 Dataset:
chronicle.manifest且kind = "leaf"CURRENT(Storyline)events.lance/_manifest.json,或名为events.lance且含_manifest.json的目录- compact-jsonl Lance(
pchronicle.format = compact-jsonl/v1,或 leaf sidecarformat = "compact-jsonl/v1")
否则该路径是 Directory(包括 chronicle.manifest 的 kind = "branch")。
MUST 忽略符号链接。现有 max_entries / max_files 遍历上限仍然适用。
import / sync 使用独立递归 JSON 扫描,不受 list 约束。
list(path) — 与 shell 一层列举一致¶
pchronicle ls PATH 与 Web Datasets MUST 只列出 PATH 的一层 children,行为对齐
shell ls。MUST NOT 把嵌套 source 摊到当前列表。
每个 child 属于:
| 子项 | list kind |
预览 |
|---|---|---|
| 叶子 Dataset 目录 | dataset | format,以及 sidecar 中的 [stats].record_count / failed_count |
| Directory | directory | 仅名称;MUST NOT 为预览做深扫 |
本层 .json / .jsonl / .ndjson |
file | 名称;open 前 format MAY 未知 |
对 叶子 Dataset 做 list 时 MUST NOT 列出 Lance 内部(data/、_versions/、
generations/、_offload/ 等)。结果就是这一条 Dataset 及其预览。继续下钻是 open /
Runs,不是再一次 ls。
pchronicle ls --sources MUST 对 path 做 open 并打印 Snapshot 的 source 表(虚拟
dataset 成员)。默认 ls 仍是一层 children。--sources MUST NOT 改变 list 本身。
父节点 MUST NOT 要求 branch manifest 里写显式 children。对 branch 或 Directory 的
list 只看一层子项:有 Dataset 标记的列为 dataset,其余目录列为 directory,
本层松散 JSON 列为 file。
open(path) — Snapshot,含虚拟 dataset¶
open(path) 构造查询 Snapshot:
- 若
path是叶子 Dataset,Snapshot 只有一个 source_file_ = "."。 即使 leaf 内部还有嵌套chronicle.manifest,也 MUST NOT 再为其产出 source。 - 若
path是 JSON / JSONL / NDJSON 文件,Snapshot 只有一个 file source。 - 若
path是 Directory,调用方 MAY 把它强行当作 query 根。 Snapshot 是 虚拟 dataset:在path下递归收集所有嵌套叶子 Dataset,以及 不在 leaf 内部的 JSON / JSONL / NDJSON。未标注的中间目录本身不是 source, 只构成_file_的路径段。_file_相对这个path。
serve 的每个挂载就是一次 open(uri)。进入挂载后的浏览是对 prefix 做 list。
Open in Runs SHOULD 沿用该 Snapshot,并 MAY 用 _file_ 收窄。Runs 页 MAY 继续用
run 摘要重建路径树(PathExplorer);那棵树不是 Datasets 的 list。
pchronicle query ./warehouse/team 是对该 URI 的另一次 open。
branch manifest 仍表示「此节点用于嵌套子节点」。对 branch 或无 sidecar 的 Directory
做 open,走同一套虚拟 dataset 遍历。对两者做 list 仍只列一层。
Branch 聚合与轨迹计数¶
Branch 节点 MAY 省略 [stats],且 MUST NOT 被当成轨迹 source。
只有 leaf 贡献轨迹数。
open 之后的 Dataset 预览,以及 list 上的叶子卡片:
- 叶子的轨迹数在 sidecar 存在且 fingerprint 可信时,用
[stats].record_count(以及failed_count); - 对 Directory 做
listMUST NOT 上卷子孙计数(那是深扫)。上卷属于 Snapshot /open; - 中间 branch 自身不作为 source;
- leaf MUST NOT 再向下递归寻找嵌套 source,避免物理 leaf 与子 leaf 双计。
禁止祖先回写(写放大)¶
发布或更新某个 leaf 时,MUST 只更新该 leaf 的 chronicle.manifest。
写入方 MUST NOT 为缓存累计总数而改写祖先 branch manifest。Branch 文件 SHOULD
仅作描述,例如:
文件夹累计数属于读侧职责。
进程内刷新缓存¶
Warehouse / Catalog MAY 在进程内缓存 list 的 children 与可信 leaf stats,使周期性
UI 刷新不必重开 Lance。当 leaf 的 fingerprint / manifest mtime 变化,或一层 children
中出现新的 chronicle.manifest 时,缓存条目 SHOULD 失效。进程缓存 MUST NOT
取代随数据一起分发的磁盘 leaf manifest 作为真相源。list 刷新 MUST NOT 跑
acceleration SQL 或 steps 的 token/duration 查询。
TOML schema(v1)¶
顶层必填¶
| 字段 | 类型 | 规则 |
|---|---|---|
schema_version |
integer | 本 RFC MUST 为 1 |
kind |
string | MUST 为 "leaf" 或 "branch" |
仅 Leaf¶
| 字段 | 类型 | 规则 |
|---|---|---|
format |
string | kind = "leaf" 时 MUST 存在;v1 写入方 MUST 使用 compact-jsonl/v1 或 storyline/v1 |
未知 format 值 MUST 被通用读者保留;特定格式 opener MAY 拒绝不支持的值。
[identity]¶
| 字段 | 类型 | 规则 |
|---|---|---|
fingerprint |
string | 存在 [stats] 时 MUST 存在;把 stats 绑定到物理修订 |
compact-jsonl v1 的 fingerprint SHOULD 为 lance:version:<N>,其中 <N> 是写入后发布的
Lance dataset version。
[stats]¶
| 字段 | 类型 | 规则 |
|---|---|---|
record_count |
integer ≥ 0 | leaf compact-jsonl 写入方 MUST 提供 |
failed_count |
integer ≥ 0 | MUST 提供;未知/无失败时用 0 |
min_timestamp |
string | MAY 省略 |
max_timestamp |
string | MAY 省略 |
total_tokens |
integer ≥ 0 | MAY 省略 |
后续 schema 版本 MAY 增加更多 stats 键;v1 读者 MUST 忽略 [stats] 下的未知键。
Leaf 示例¶
schema_version = 1
kind = "leaf"
format = "compact-jsonl/v1"
[identity]
fingerprint = "lance:version:1"
[stats]
record_count = 12345
failed_count = 0
min_timestamp = "2026-01-01T00:00:00Z"
max_timestamp = "2026-09-07T01:00:00Z"
Branch 示例¶
写路径¶
chronicle.manifest是 store 层标准机制:compact-jsonl 的唯一发布出口是CompactJsonlStore::publish_manifest/import_path。CLIimport与syncMUST 只通过该 store API,不得在上层另写并行 sidecar。- Compact JSONL
import/ 成功 republish /syncsnapshot MUST 在输出 dataset 根写入chronicle.manifest。 - Storyline
import(--output-format storyline)MUST 在每次分批 commit 后更新输出根上的 leafchronicle.manifest(format = "storyline/v1",record_count为已提交累计条数), 以便 Warehouse catalog / explorer 在导入过程中观察到进展。 - 本机文件系统上的写入 MUST 原子(写临时文件再 rename)。
- 物理写入成功后,
fingerprintMUST 匹配已发布修订,且[stats].record_countMUST 等于已发布行数。 - 若 dataset 写成功但 manifest 写失败,
import_pathMUST 失败(不发布半成品契约);对 仅ensure_manifest的只读升级路径,失败 MAY 记日志并继续打开物理数据。
读路径与过期¶
- 当
fingerprint与已打开物理修订一致时,读者 MAY 信任[stats]做ls/ Datasets 预览,而无需扫行。 - 文件缺失、不可读或 fingerprint 不匹配时,store 层
ensure_manifestSHOULD 就地补写; 若补写失败,读者 MUST 回退现有 discovery / summary 路径。 - Manifest stats MUST NOT 成为查询正确性的唯一权威;SQL 与 record 列表仍读物理存储。
对 Warehouse Datasets / ls 的影响¶
- CLI 默认
ls与/api/explorer/tree(或其后继)MUST 是同一个list(path):一层 children, Dataset 子项在有chronicle.manifest时带预览。 pchronicle ls --sourcesMUST 打印open(path)的 Snapshot 成员。SQLdataset.sources仍是查询内的等价视图。- Web Datasets MUST NOT 用
RunSummary、explorer_weight、shallow-nav 回退或other溢出桶反推文件夹。 - Runs 的 Run paths 树 MAY 继续按
_file_/ import path 分组(run 反推树)。 该面属于 Runs,不属于 Datasets。 - 单击 Directory 即导航(对该 prefix 再
list)。单击叶子 Dataset 不得钻进 Lance 内部。 Open in Runs 对当前 query 根(serve 挂载)做open,并 MAY 用_file_过滤。 - 详细 run/record 页仍可打开物理 leaf;本 RFC 不要求 sidecar 索引每条 record 身份。
必需单元测试(v1)¶
实现 MUST 至少覆盖:
list只列一层:warehouse/(Directory)下有team/(Directory)与archive/(leaf, N)时,列出team/为 directory、archive为 dataset 且record_count = N。MUST NOT 列出team/codex_jsonl。open是虚拟 dataset:open(warehouse)得到_file_ = team/codex_jsonl的 compact source,record_count = N(以及其它兄弟 leaf / 不在 leaf 内的 JSON), 且在 leaf manifesto 存在时不打开 Lance。- 对叶子做
list:list(archive)只报告这一条 Dataset 及预览,MUST NOT 列出data/或_versions/。 open时 Leaf 不递归:leaf 目录内即使还有嵌套chronicle.manifest,也 MUST NOT 再为该子节点额外产出 source。- 写隔离(契约):只更新某一个 leaf 的 manifesto 后,无需改动父 branch 文件,该
leaf 的
list预览仍正确。 - CLI/UI 一致:同一 path 上 Web Datasets 的 children 与
pchronicle ls的名称、 kind、Dataset 预览一致。 ls --sources:ls --sources warehouse列出team/codex_jsonl(以及其它 Snapshot 成员),且 MUST 与open(warehouse)/dataset.sources一致。
兼容性¶
- 没有
chronicle.manifest的旧 dataset 仍然有效;首次经 store 打开或启发式 discovery 确认 compact-jsonl 时,SHOULD 自动补写 sidecar。 - Lance schema metadata
pchronicle.format = compact-jsonl/v1仍是物理格式标记;sidecar 不替代它。 - 对象存储 URI 不在 v1 写入范围内;远端读取可在以后用同一 schema 扩展。对象存储上的嵌套
branch 扫描 MAY 使用前缀列举 + 精确读取
chronicle.manifest;v1 不要求 S3 按文件名 glob 搜索。
曾考虑的替代方案¶
- 扩展 Lance
_versions/*.manifest— 否决:二进制 MVCC、非 pChronicle 所有、不适合嵌套与 UI 统计。 - 仅 Warehouse 内存缓存作为唯一统计存储 — 否决:不随数据走,进程重启即失效。进程缓存 仍可作为磁盘 leaf manifesto 之上的刷新优化。
- 父节点显式
children列表 — 延后:自动扫描更贴合目录树,也避免子列表过期。 - 把累计
[stats]回写到每个祖先 branch — v1 否决:单 leaf 更新会写放大,且易产生脏父节点。 - 用 run 摘要 / acceleration 反推 Datasets 文件夹 — 否决用于 Datasets / 默认
ls。 Runs 的 Run paths 树 MAY 继续按 run_file_/ import path 分组。 - 默认
ls打印 Snapshot 成员 — 否决。默认ls对齐 shell。ls --sources与dataset.sources仍是 Snapshot 成员视图。