Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,38 @@ consumer chain, including a committed-but-lost native reply. Its discovered rese
fall-through is a renderer recovery defect; no further native write change is
required by that correction.

## SQLite namespace diagnostics

The streaming-budget test inspects a temporary Native SQLite database directly.
Its three statistics queries must support both the installed container v3
(`roost_kv.space`) and container v4's interned namespaces (`roost_spaces.name`
joined by `space_id`). A test-local relation exposes the same `space/key/value`
columns for either format, with an explicit accepted-version assertion. Update
bytes, receipt count and projection-row budgets remain unchanged. The diagnostic
relation changes no production read/write behavior. Companion storage work:
[Roost #15](https://github.com/loro-dev/roost/pull/15).
Diagnostic compatibility PR: [#1735](https://github.com/LodyAI/Lody/pull/1735).

The coordinated Roost 0.2.0 release is now published. PR #1735 pins the
TypeScript package in the shared catalog and Native exactly in the CLI manifest
and publication guard, retaining the published-runtime version contract. It keeps all six exact
platform dependencies in the lockfile and release-age exceptions. This upgrades
the Native runtime to SQLite container v4; earlier containers are refused, with
no migration, automatic reset or data cleanup. The dependent
[message-state fix #1737](https://github.com/LodyAI/Lody/pull/1737) consumes this
same catalog update and must merge after #1735. Acceptance is repeated against
the published packages rather than a local SDK artifact.

Frozen installation, complete `pnpm check`, repository format and documentation
checks pass with the published 0.2.0 packages and no native override. The full CLI
suite passes 4,429 tests with four existing skips, including streaming storage
budgets and reopen. The four Electron native-staging tests pass: foreign target
selection uses synthetic bindings; the installed host binding executes real
signed SQLite write/reopen probes. The Native release manifest identifies merged
Roost source `e85c970`; this is not cross-platform runtime acceptance on this host.
The public CLI build also passes at a 2 GiB heap, including its published-bundle
runtime probes; all six existing dependency-version policy tests pass.

## Outbox and existing data

Outbox rows are not pending-delivery counts. The installed TypeScript Store's ACK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,30 @@ Native 写入与其他客户端的增量正文读取,不足以作为发行验
把生产 RPC 加解密端点和两个渲染器副本加入同一消费者链路,并覆盖原生提交成功但回复丢失。
其中发现的 reset 快捷路径是渲染器恢复缺陷;这项更正不需要继续修改原生写入。

## SQLite namespace 统计

流式预算测试直接检查临时 Native SQLite 数据库。三处统计查询同时支持当前安装的
container v3(`roost_kv.space`)和 namespace 整数引用的 v4(通过 `space_id`
关联 `roost_spaces.name`)。测试内的查询关系为两种格式提供相同的
`space/key/value` 列,并显式断言支持的版本;更新字节、回执行数和投影行数预算不变。
统计查询本身不改变生产读写行为。
配套存储改动:[Roost #15](https://github.com/loro-dev/roost/pull/15)。
统计兼容 PR:[#1735](https://github.com/LodyAI/Lody/pull/1735)。

Roost 0.2.0 已统一发布。PR #1735 在共享 catalog 固定 TypeScript 包,在 CLI
清单及发布校验中精确固定 Native,沿用运行时依赖发布合同。锁文件及发布冷却期
豁免保留全部六个精确匹配的平台包。Native 运行时
因此升级到 SQLite container v4,拒绝更早容器,不迁移、不自动重置、不清理数据。
依赖它的[消息状态修复 #1737](https://github.com/LodyAI/Lody/pull/1737) 复用同一次
catalog 更新,在 #1735 后合并。验收改用 npm 正式包,不再使用本地 SDK 产物。

使用 npm 正式 0.2.0 包、没有 Native 覆盖时,冻结安装、完整 `pnpm check`、
全仓格式和文档检查通过。CLI 全量 4,429 项通过、4 项原有跳过,包含流式存储预算
及重开。Electron 四项 Native staging 测试通过:其他平台使用合成绑定验证选择,
本机安装的真实绑定执行签名 SQLite 写入及重开。Native 发布 manifest 对应已合并的
Roost 源码 `e85c970`;这不代表在本机完成其他平台的运行时验收。
公开 CLI 在 2 GiB 堆上限内构建通过,包含发布包运行时探针;六项已有依赖版本策略测试通过。

## Outbox 与既有数据

Outbox 行数不等于待发送数量。已安装 TypeScript Store 的 ACK 推进消费者游标而不删除行,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Roost message copies in state and batch receipts

Status: implemented
Type: bug-fix
Translation: current
Date: 2026-10-20

[中文](2026-10-20-roost-message-storage-copies.zh.md)

## Abstract

Post-seal corrections copied an entire message into state and SDK command
receipts. State now contains operations relative to its exact sealed primary;
the execution owner uses Roost's local batch recovery journal. A synthetic
432 KiB unchanged body with 32 corrections retained 4,113 update bytes instead of
889,477, preserving the sealed primary, logical reads, legacy state reads and
idempotent recovery. The consumer now uses published Roost 0.2.0 through the
dependency update in #1735; older SQLite containers are not migrated or reset.

## Decision and scope

- State records contain only changed fields/text/array membership, anchored to
the primary Turn ID. Reads reconstruct the full application entry. Existing
whole-entry states remain readable within a compatible container and become deltas on their next mutation;
sealed records and retained updates are never rewritten or pruned.
This adds no SQLite-container migration. Reopening delta state requires this
adapter or newer; RPC readers still receive complete logical entries. State projection reuses the decoded
primary, and incremental state generation converts operation offsets without
serializing the accumulated text or unchanged body again.
- Local batch plans and compact pending receipts are prepared atomically by the
SDK; effects, publication, final seal and plan retirement commit atomically.
The CLI owns the full database. Turn exports synchronize committed history;
moving unfinished local work requires that database, including the journal.
The existing SDK replicated recovery default remains available to other hosts.
- This replaces part of the earlier
[write-amplification decision](2026-10-16-roost-storage-write-amplification.md),
not the guarded native streaming path, immutable human input or branch saga.
- Consume the coordinated Roost 0.2.0 release through the dependency update
in [#1735](https://github.com/LodyAI/Lody/pull/1735), then merge #1737 after it.
Native 0.2.0 requires SQLite container v4. Do not deploy, migrate/reset databases,
clean history, truncate WAL, merge PRs or publish packages in this change.

## Acceptance and failure boundaries

Tests verify production SQLite projections through seal, post-seal correction, later
streaming, permission overlay, close/reopen and lost native replies. Keep a large
unchanged text fixture out of every state update and batch receipt, measuring
retained update bytes rather than WAL length. Test Unicode offsets, field
removal, nested arrays and invalid base/path data. A malformed state fails its
existing history read; it never selects a different backend or retries a write.
Journal preparation/apply failures remain SDK-owned, with no Lody recovery loop.

## Verification

The consumer implementation is [Lody #1737](https://github.com/LodyAI/Lody/pull/1737);
the SDK implementation is [Roost #16](https://github.com/loro-dev/roost/pull/16).
The branch is rebased on [#1735](https://github.com/LodyAI/Lody/pull/1735), using
published SDK and Native 0.2.0 with exact lockfile versions and no local override.
The earlier SDK 0.1.3 `TS2554` release gate is resolved by this dependency update.

- The two owning CLI test files pass **82 tests** on published Native SQLite v4.
Cases cover legacy whole-message state reopen/conversion, a committed first-state
reply lost before projection, continued output, permissions, branch/input
contracts, Unicode, field removal, invalid bases/paths and own JSON keys.
- Earlier paired synthetic runs compare Lody `7d6695c` with SDK 0.1.3 against this
change with the proposed SDK. Both retain **37 updates** after sealing a
442,368-byte unchanged body and applying 32 corrections. Update value bytes fall
**889,477 → 4,113 (99.54%)**; the largest update falls **443,344 → 488**. The initial
primary, outbox copy, indexes, WAL and stream transport framing are excluded.
Both preserve old frames, primary immutability, logical content and reopen after
the lost reply. The current regression enforces bounded growth, not exact wire bytes.
- Complete `pnpm check` passes with the published packages, including all package
typechecks, ordinary typed lint, Effect lint, repository tests, i18n and boundaries.
The full CLI suite passes **4,435 tests with four existing skips**. Repository
formatting and documentation checks pass with no documentation errors.
- The public CLI builds at a **2 GiB heap limit** and passes its published-bundle
runtime/Worker probes. Desktop CLI staging succeeds; the actual Electron runtime
writes, verifies and reopens signed SQLite history through the staged Native
Worker. The four staging tests cover all six target selections with synthetic
foreign bindings and execute the installed host binding. This is not a local GUI
E2E run or execution of foreign-platform binaries; standard PR CI is rerun.

Merge #1735 before #1737. No application database, deployment or package publication
was changed during this acceptance.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Roost 状态与批处理回执中的消息副本

Status: implemented
Type: bug-fix
Translation: current
Date: 2026-10-20

[English](2026-10-20-roost-message-storage-copies.md)

## 摘要

封存后的消息修正此前会把整条消息复制进状态和 SDK 命令回执。现在状态只记录相对指定已封存 primary 的操作,执行机器使用 Roost 的本地批处理恢复日志。含 432 KiB 不变正文、修正 32 次的合成用例,其保留更新由 889,477 字节降为 4,113 字节,同时保留 primary 签名、完整逻辑读取、旧状态读取和幂等恢复。消费者现在通过 #1735 的依赖更新使用正式 Roost 0.2.0,不迁移或重置更早的 SQLite 容器。

## 决策与范围

- 状态只记录变化的字段、文本及数组成员,绑定 primary Turn ID,读取时重建完整应用消息。在兼容容器内,已有整条消息状态继续可读,在下次正常修改时转为差量;不重写或裁剪已封存记录及保留更新。不增加 SQLite 容器迁移。写入差量后重开数据库需要当前或更新的适配器,RPC 读取仍返回完整逻辑消息。投影复用已解码的 primary,增量生成只转换操作偏移,不再次序列化累计文本或不变正文。
- SDK 在同一事务中准备本地命令计划与精简 pending 回执,再原子提交正文操作、发布信息、最终封存与计划退役。CLI 拥有整个数据库;Turn 导出同步已提交历史,迁移未完成的本地工作须携带包含恢复日志的完整数据库。其他宿主仍可使用 SDK 原有的可复制恢复默认模式。
- 本决策替换此前[写放大修复](2026-10-16-roost-storage-write-amplification.zh.md)的部分处理,不改变带条件校验的原生流式写入、不可变人类输入和分支事务。
- 通过 [#1735](https://github.com/LodyAI/Lody/pull/1735) 的依赖更新使用统一发布的 Roost 0.2.0,再合并 #1737。Native 0.2.0 要求 SQLite container v4。本改动不部署、不迁移或重置数据库、不清理历史、不截断 WAL、不合并 PR、不发包。

## 验收与故障边界

测试通过生产 SQLite 路径验证封存、封存后修正、后续流式输出、权限投影、关闭重开及原生回复丢失。大段不变文本不得出现在每条 state 更新和批处理回执中,测量保留更新字节而非 WAL 文件长度。验证 Unicode 偏移、字段删除、嵌套数组和错误基准/路径。错误状态继续使该历史读取报错,不切换后端、不重试写入。恢复日志的准备和应用故障由 SDK 处理,Lody 不另建恢复循环。

## 验证

消费者实现为 [Lody #1737](https://github.com/LodyAI/Lody/pull/1737),SDK 实现为 [Roost #16](https://github.com/loro-dev/roost/pull/16)。分支已基于 [#1735](https://github.com/LodyAI/Lody/pull/1735) rebase,使用正式 SDK 与 Native 0.2.0,锁文件精确固定版本,没有本地覆盖。此前 SDK 0.1.3 的 `TS2554` 发布阻塞已通过依赖更新解决。

- 两个所属 CLI 测试文件在正式 Native SQLite v4 上 **82 项通过**。覆盖旧整条消息状态重开及转换、首次状态已提交但投影前丢失回复、后续输出、权限、分支和输入合同、Unicode、字段删除、错误基准/路径及 JSON 自有键。
- 此前的配对合成运行对照 Lody `7d6695c` + SDK 0.1.3,与本改动 + 拟发布 SDK。两边在封存 442,368 字节不变正文并修正 32 次后,均保留 **37 条更新**。更新 value 由 **889,477 降至 4,113 字节(99.54%)**,最大单条由 **443,344 降至 488 字节**。此口径不含初始 primary、outbox 副本、索引、WAL 和流传输封装。两边均保留旧帧、primary 不可变、逻辑内容及丢失回复后的重开。当前回归限制增长上界,不锁定协议精确字节数。
- 使用正式包的完整 `pnpm check` 通过,包含所有包类型检查、普通类型感知 lint、Effect lint、仓库测试、i18n 和边界检查。CLI 全量 **4,435 项通过、4 项原有跳过**。全仓格式及文档检查通过,文档无错误。
- 公开 CLI 在 **2 GiB 堆上限**内构建并通过发布包运行时及 Worker 探针。桌面 CLI staging 成功;真实 Electron 运行时通过 staged Native Worker 写入、校验并重新打开带签名的 SQLite 历史。四项 staging 测试覆盖六个平台的选择,其他平台用合成绑定,本机执行安装的真实绑定。这不代表本地 GUI E2E 或其他平台二进制的运行验收;正式 PR CI 已重跑。

先合并 #1735,再合并 #1737。本轮验收没有修改应用数据库、部署或发布包。
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ history coverage and device-level responsiveness remain validation limits.

## Decision and boundaries

Pin `@loro-dev/roost-node@0.1.2` as a CLI runtime dependency and exempt only that
version and its six exact platform dependencies from the release-age policy.
Keep `@loro-dev/roost@0.1.2` for identity,
application JSON, and `NodeLodyHistory`. Neither Rust source nor a sibling Roost
checkout is needed to install, build, or package Lody.
Pin `@loro-dev/roost-node@0.2.0` as an exact CLI runtime dependency, with
`@loro-dev/roost@0.2.0` from the shared catalog for identity, application JSON, and
`NodeLodyHistory`. Exempt only these exact versions and the six matching platform
dependencies from the release-age policy. Native 0.2.0 uses SQLite container v4;
it refuses earlier containers without migration or automatic reset. Neither Rust
source nor a sibling Roost checkout is needed to install, build, or package Lody.

Use `RoostNativeClient` directly. Preserve the database path, credential-store
seed, admitted owner, bounded queue, shared stream leases, and awaited final close.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ CLI 现在接入已发布的 Node-API 包,在 Worker 内运行 SQLite 实现

## 决策与边界

CLI 运行依赖固定为 `@loro-dev/roost-node@0.1.2`,发布冷却期仅豁免该版本及
六个精确匹配的平台包版本。
保留 `@loro-dev/roost@0.1.2` 的身份、应用 JSON 和 `NodeLodyHistory` 适配器。
CLI 将运行依赖精确固定为 `@loro-dev/roost-node@0.2.0`,身份、应用 JSON 和
`NodeLodyHistory` 使用共享 catalog 的 `@loro-dev/roost@0.2.0`。
发布冷却期仅豁免这两个精确版本和六个匹配的平台包版本。
Native 0.2.0 使用 SQLite container v4,拒绝更早容器,不迁移或自动重置。
安装、构建和打包 Lody 无须 Rust 源码或旁边的 Roost 仓库。

直接使用 `RoostNativeClient`,保留数据库位置、系统凭据存储中的种子、允许的签名身份、
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@lody/shared": "workspace:*",
"@lody/turn-diff-store": "workspace:*",
"@loro-dev/flock-wasm": "catalog:",
"@loro-dev/roost": "0.1.3",
"@loro-dev/roost": "catalog:",
"@loro-dev/streams-crdt": "catalog:",
"@loro-dev/streams-client": "catalog:",
"@modelcontextprotocol/client": "2.0.0",
Expand Down Expand Up @@ -128,7 +128,7 @@
],
"dependencies": {
"@effect/platform-node-shared": "catalog:",
"@loro-dev/roost-node": "0.1.2",
"@loro-dev/roost-node": "0.2.0",
"@lydell/node-pty": "1.2.0-beta.14",
"@napi-rs/keyring": "2.1.0",
"better-sqlite3": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/scripts/check-published-bundle-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const requiredPublishedRuntimeDependencies = [
const requiredExactPublishedRuntimeDependencies = ['loro-crdt'];
const requiredPinnedPublishedRuntimeDependencies = new Map([
['@lydell/node-pty', '1.2.0-beta.14'],
['@loro-dev/roost-node', '0.1.2'],
['@loro-dev/roost-node', '0.2.0'],
]);

for (const block of dependencyBlocks) {
Expand Down
12 changes: 11 additions & 1 deletion apps/cli/src/session/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,17 @@ it never authorizes switching back to Loro or reading a superseded published suf
ACP batch appliers can mutate item arrays. The Roost adapter applies them to a
detached working copy, preserving the before-image used to decide what to persist.
Content changes refresh only affected primary bodies. Sealed-turn corrections
use mutable SDK state records anchored to their primary; permission responses
use `lody-message-state` version 1 records: field, Unicode-text and array operations
relative to the exact immutable primary Turn ID. The owner reconstructs logical
entries for existing RPC readers. Whole-entry state records remain readable in a
supported container and convert on their next mutation; sealed primary bytes stay
unchanged. Reopening delta states requires this adapter or newer. SDK fallback
batches use owner-local recovery: the compact pending
receipt and private journal commit together, then effects, publication, seal and
journal retirement commit together. Moving unfinished work requires the whole
owner database and its paired host identity; Turn exports carry committed history.
The [message-copy repair](../../../../.agents/notes/implemented/bug-fix/2026-10-20-roost-message-storage-copies.md)
records the format, release dependency and measured storage reduction. Permission responses
remain independent SDK records projected onto the matching tool. Responding does
not seal the assistant or interrupt its output.
Existing eligible unsealed primary/state records use guarded native field/text edits,
Expand Down
6 changes: 5 additions & 1 deletion apps/cli/src/session/roost-content-patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ export function roostContentPatches(before: unknown, after: unknown): ContentOp[
}
if (object(left) && object(right)) {
for (const key of new Set([...Object.keys(left), ...Object.keys(right)]))
walk(left[key], right[key], [...path, { key }]);
walk(
Object.hasOwn(left, key) ? left[key] : undefined,
Object.hasOwn(right, key) ? right[key] : undefined,
[...path, { key }]
);
return;
}
ops.push({ kind: 'set', path, contentJson: encodeRoostContentText(right) });
Expand Down
Loading
Loading