@quolu/lattice 0.28.0 → 0.30.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.
package/README.md CHANGED
@@ -1,164 +1,211 @@
1
+ <p align="center">
2
+ <img src=".github/og.png" alt="Lattice — stop serializing work that only looks like it conflicts" width="100%">
3
+ </p>
4
+
1
5
  # Lattice
2
6
 
3
- Latticeは、codebaseの境界を観測・変換し、multi-agent開発の並列TODO graphを生成する
4
- schedulability compilerです。
7
+ [![npm](https://img.shields.io/npm/v/@quolu/lattice?color=cb3837&logo=npm)](https://www.npmjs.com/package/@quolu/lattice)
8
+ [![CI](https://github.com/kitepon-rgb/Lattice/actions/workflows/ci.yml/badge.svg)](https://github.com/kitepon-rgb/Lattice/actions/workflows/ci.yml)
9
+ [![license](https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-blue)](LICENSE)
10
+ [![node](https://img.shields.io/node/v/@quolu/lattice?color=339933&logo=node.js&logoColor=white)](https://nodejs.org/)
11
+ [![patent](https://img.shields.io/badge/patent-pending%20JP%202026--178950-6366f1)](#patent)
5
12
 
6
- ## 開発工場での位置づけ
13
+ **English** · [日本語](README.ja.md)
7
14
 
8
- Latticeは[dotagents開発工場](https://github.com/kitepon-rgb/dotagents)が管理する
9
- 自作コア10製品の一つです。本repoはplan/ToDo/run store、sensor、schema、migration、
10
- release、diagnosticsを所有し、dotagentsは製品横断の工程利用・導入・host統合を所有します。
11
- 廃止済みの前身sensorは独立製品として配線せず、Lattice sensorだけを現役面とします。
12
- MarkItDownは別区分の第三者CLIです。
15
+ > **Stop serializing work that only looks like it conflicts.**
16
+ > Lattice is a schedulability compiler for multi-agent development. It observes the real
17
+ > boundaries of your codebase, proves which tasks can run in parallel, and — when two tasks
18
+ > genuinely collide — **refactors the seam between them and recompiles the plan** so they can
19
+ > run in parallel after all.
13
20
 
14
- 現在の工程状態と完了証拠の正本は、このrepoのLattice storeです。文書の役割と現行導線は
15
- [docs/README.md](docs/README.md)、製品思想は[PLAN.md](PLAN.md)、公開contractは
16
- [docs/00_product-contract.md](docs/00_product-contract.md)を参照してください。
21
+ ## Why
17
22
 
18
- CLIの全体像は`lattice --help`、各公開namespaceの正規構文は
19
- `lattice <plan|run|event|todo|sensor|factory-diagnostics|runtime-errors|bridge> --help`で確認できます。
20
- 個別操作は`lattice <namespace> <subcommand> --help`または`lattice help <namespace> <subcommand>`で
21
- 正規optionをstore非依存に確認できます。
23
+ Give three coding agents three tasks and the usual outcome is one of two failures:
22
24
 
23
- ## 開発
25
+ - **You serialize too much.** "These both touch `renderer.ts`, so run them one at a time."
26
+ Often they touch *different symbols* in that file and could have run together.
27
+ - **You serialize too little.** Nothing declared a dependency, so you run them in parallel and
28
+ discover the collision after both have written conflicting code.
29
+
30
+ Both failures come from the same gap: *nobody actually measured the boundary.* Dependency
31
+ arrows in a task list are a claim about intent, not evidence about code.
32
+
33
+ Lattice closes that gap with a different move. It does not just **detect** the conflict — it
34
+ **removes** it. When two tasks contend for one file, Lattice derives a cut, applies it in an
35
+ isolated worktree, verifies the transform against five acceptance conditions, and recompiles the
36
+ plan against the transformed source. The conflict edge disappears because the shared surface
37
+ stopped being shared.
38
+
39
+ ## What it actually does
24
40
 
25
- ```bash
26
- npm test
27
- npm run check
28
- npm run ci
29
- node scripts/reap-orphan-test-daemons.mjs
30
- lattice sensor sync . --json
31
- spotter doctor
32
- codex-sidecar diagnostics --project . --preset auditor --json
41
+ ```
42
+ declare boundaries → compile independence → conflict?
43
+ ├─ no → run in parallel
44
+ └─ yes → propose a seam
45
+ transform in an isolated worktree
46
+ verify (5 conditions)
47
+ land + recompile → run in parallel
33
48
  ```
34
49
 
35
- `reap-orphan-test-daemons.mjs`は、実daemonを起動するtestが取り残したprocessを一覧します。既定は表示
36
- だけで何も停めません。停めるのは`--reap`を付けた時に限り、対象はargvが指すfixtureのdirectoryが既に
37
- 存在しないものだけです。実行中のtestを巻き込まないための条件なので、fixtureが残ったまま死んだ実行を
38
- 含めたい場合だけ`--older-than-hours=<n>`で起動時刻による許可を明示します。
50
+ **A real example, from this repository.** Two tasks both needed to change
51
+ `src/seam-commit.mjs`. Lattice compiled the declarations, reported `conflict_count: 1` with
52
+ `severability: code_seam`, proposed a cut, and applied it in an isolated worktree. After all
53
+ five acceptance conditions passed, the file was split into one owned surface per task plus a
54
+ shared and a residual surface. Recompiling reported `conflict_count: 0` and placed both tasks in
55
+ the same parallel group.
39
56
 
40
- 未初期化projectで`sensor sync`した場合は`LATTICE_SENSOR_NOT_INITIALIZED`と正規`next_action`を返します。
41
- その他のsensor失敗もexit code、signal、bounded stderrをtyped detailへ残し、原因を隠しません。
57
+ Nobody hand-refactored that file. The product cut it so the work could parallelize.
42
58
 
43
- Node.js 22.13以上を使用します。境界観測は配布物に同梱したLattice sensorだけを使い、PATH上の
44
- 廃止済みruntimeや旧cache/dataへfallbackしません。Spotterはproject単位で生成stateの所有境界を守ります。
59
+ ### The five acceptance conditions
45
60
 
46
- どのrepoでも、Latticeの導入状態はdirectoryの有無を推測せず、最初に次のtyped discoveryで判定します。
61
+ A transform is adopted only when **all five** hold. One missing condition rejects it:
47
62
 
48
- ```bash
49
- lattice status --json
50
- ```
63
+ | Condition | Meaning |
64
+ |---|---|
65
+ | `behavior_equivalent` | The original path's public export surface is preserved |
66
+ | `focused_tests_passed` | The affected tests actually pass against the transformed source |
67
+ | `sensor_fresh` | The structure index was rebuilt and covers the new surfaces |
68
+ | `overlap_reduced` | The target conflict is gone **and** plan-wide conflict pairs did not increase |
69
+ | `parallelism_improved` | The number of execution waves went down |
70
+
71
+ ### Runtime, not just planning
51
72
 
52
- `state`は`uninitialized | ready | active_run | invalid`のいずれかです。`uninitialized`は
53
- 正常な未初期化状態で、`next_action`が正規の初期authoring入口を返します。初回planは
54
- 新規planはPhase監査とToDo schedulingを分離する`lattice.plan_create_input.v3`のcanonical
55
- JSON+LFを用意し、次で作成します。既存v2/v4は互換契約として維持されます。
73
+ Complete separation is not obtainable at planning time — dynamic dispatch, runtime-resolved
74
+ paths, and external state always leave residue. That is the design, not a deficiency: Lattice
75
+ carries a second stage at runtime.
76
+
77
+ While work executes, Lattice observes **what was actually changed**, not what was declared. When
78
+ it sees a task writing outside its declared scope, or into another running task's scope, it
79
+ raises a runtime conflict — and can either hold one side while the other commits, or transform
80
+ the seam and resume both.
81
+
82
+ ## Install
56
83
 
57
84
  ```bash
58
- lattice plan create --schema-version 3 --json
85
+ npm install -g @quolu/lattice
59
86
  ```
60
87
 
88
+ Requires **Node.js 22.13+**. The structure sensor ships inside the package — there is nothing
89
+ else to install, and Lattice never falls back to a sensor on your `PATH`.
90
+
91
+ ## Quick start
92
+
93
+ Every project begins with typed discovery. Never guess from directory layout:
94
+
61
95
  ```bash
62
- lattice plan create --input .lattice/plan-create.json
96
+ lattice status --json
63
97
  ```
64
98
 
65
- `invalid`をMarkdown fallbackへ丸めず、`next_action`に従ってstoreを診断してください。
66
- discoveryと初期transactionの不変条件は
67
- [ADR 0058](docs/adr/0058-project-discovery-and-initial-authoring.md)が正です。
99
+ `state` is one of `uninitialized | ready | active_run | invalid`, and `next_action` gives the
100
+ canonical next command. Then index the codebase and declare boundaries:
68
101
 
69
- ## 実行runを端から端まで動かす
102
+ ```bash
103
+ lattice sensor init . --json
104
+ ```
70
105
 
71
- compileしたrunを実際にdispatchするには、executor adapterを登録してからactivateします。
72
- 参照実装の`lattice-scripted-adapter`を配布しているため、公開CLIと配布binだけで
73
- 実write・receipt受理・closeまで到達できます。
106
+ Write a draft declaring what each task owns, then let the tool supply the parts you cannot
107
+ hand-write — fresh observations, provenance wiring, canonical bytes:
74
108
 
75
109
  ```bash
76
- lattice run adapter register --schema --json # 登録入力のJSON Schema
77
- lattice run adapter register --input adapter.json
78
- lattice run adapter list --json
79
- lattice run activate --run .lattice/runs/<id>
80
- lattice run status --run .lattice/runs/<id> # accepted に子が入る
81
- lattice event verify --run .lattice/runs/<id>
82
- lattice run close --run .lattice/runs/<id>
110
+ lattice todo independence witness scaffold --plan <key> --input draft.json
111
+ lattice todo independence compile --plan <key> --input .lattice/todo/witness/<key>.json
112
+ lattice todo independence --plan <key> --json
83
113
  ```
84
114
 
85
- digestは手で計算しません。binary・config・capabilities・自己digestは登録時にCLIが導出します。
86
-
87
- `plan compile`が`BOUNDARY_UNKNOWN`を返す場合は、まず`git status --short`が空かを確認してください。
88
- 未追跡ファイルがあるとsensor statusが`stale`になり、witnessが未解決unknownへ落ちます。
89
- 作業ツリーをcleanにすると同じrequestがそのまま通ります。
90
-
91
- TODO工程storeの読取は`lattice todo status`、`compile_binding`付きTaskの投影は
92
- `lattice todo bindings`、検証は`lattice todo verify`、表示生成は
93
- `lattice todo gantt`を使います。topology/source reconciliationは
94
- `lattice todo revise --plan <key> --input <canonical-revision.json>`、Phase付きplanは
95
- `lattice todo revise-phase --plan <key> --input <canonical-phase-revision.json>`でsuccessor発行します。
96
- cross-plan topologyを同時に切り替える場合は
97
- `lattice todo revise-set --input <canonical-revision-set.json>`を使い、Phase revisionを含む集合は
98
- `lattice.todo_revision_set.v3`で通常revisionと混在できます。
99
- Phase付きv5 planでは、通常ToDoの開始順はToDo DAGだけで決まり、Phase前後関係は重監査の順序だけを
100
- 制御します。特定ToDoがPhase受理を本当に必要とする場合だけ`phase_accept_dependencies`で明示します。
101
- `lattice todo status --json`の`dispatch_frontier`はready全件を同時dispatchする既定を示します。
102
- readyが複数なら最初のstartに`--parallel-frontier`を付け、subsetだけを直列着手する場合は
103
- `--override-reason <reason>`で理由を残します。
115
+ If the verdict reports a conflict with `severability: code_seam`, ask for a cut and apply it:
104
116
 
105
117
  ```bash
106
- lattice todo start --plan <key> --task <id> --parallel-frontier
107
- lattice todo start --plan <key> --task <id> --override-reason <reason>
118
+ lattice todo seam-proposal compile --plan <key>
119
+ lattice todo seam-proposal apply --plan <key> # isolated worktree, five conditions
120
+ lattice todo seam-proposal land --plan <key> --names names.json
108
121
  ```
109
122
 
110
- `--parallel-frontier`はhostへ並列dispatch方針を宣言する開始gateです。Lattice自身がAI hostのagentを
111
- 起動するものではなく、実際のdispatchはhostが行います。宣言後もready全件が着手されたかは
112
- `active_set`と`next_ready`で観測できます。
113
- ToDo完了は軽量確認までで、所属ToDoが全てdoneになったPhaseは`gate_ready`となり、`todo phase review`後に
114
- required evidenceを束縛した`todo phase accept`で重監査の判断を記録します。監査回数やPhase数を自動追加する
115
- 機能ではありません。Phase状態は
116
- `lattice todo phase status --plan <key>`、閲覧中に進捗が更新される工程表は
117
- `lattice todo gantt serve --port 0`で確認できます。live viewerはloopback-only、read-onlyで、
118
- `/projects/<project_id>/`というproject固有URLを返します。別projectからそれぞれ起動すれば、独立port・独立SSE経路で同時表示できます。
119
- session開始時のtyped discoveryで使う`lattice status --json`と、actor環境変数を持つ通常のTODO操作は
120
- active projectを自動登録し、一つのloopback dashboard daemonを再利用します。
121
- `/projects/`の一覧からproject固有の工程図を開け、各projectのSSE更新は互いに分離されます。
122
- dashboardはmanifestのfile identityが変わらない間のstable store readを再利用します。
123
- 巨大工程図のrender中にhealth応答が遅れても、生存中dashboardを新daemonで置き換えず
124
- `DASHBOARD_DAEMON_UNRESPONSIVE`としてtyped拒否します。
125
- 最近のsession activityが期限切れでも、Lattice storeの`active_set`が非空なprojectは一覧へ残ります。
126
- 長時間の外部処理中にCLI呼出しが途切れても進行中projectを休眠扱いしません。
127
- LANや外部reverse proxyから閲覧するoptional bridgeは既定で無効です。明示したIPにだけbindする初回設定、
128
- 再設定、停止方法は[bridge setup](docs/bridge-setup.md)を参照してください。reverse proxy hostへsshで
129
- 到達できる場合は、LANへbindせずloopbackだけを逆トンネルで公開する構成も選べます。
130
- 工程図の既定表示は、後続に作業中・未着手が残っていない完了工程を図から除きます。まとめnodeも置かないため、
131
- 完走したplanは図の場所を取りません。除いた工程は凡例の件数、右ペインの「全工程」一覧、各工程の詳細から
132
- 辿れ、詳細の前提・後続は除外前の依存関係を示します。総数・進捗・最長依存鎖は除外前の全工程で数えます。
133
- 凡例の件数バッジを押すと全工程を描いた図へ切り替わり、`lattice todo gantt --scope all`は最初から全件を
134
- 描きます。表示規約は[ADR 0066](docs/adr/0066-gantt-live-scope-drops-finished-work.md)が正です。
135
-
136
- 右ペインは概要・選択工程・全工程の3面で、いずれもToDo storeを表示します(元plan Markdown本文は
137
- 再表示しません。元文書へは各工程の詳細が持つ行対応から辿ります)。全工程一覧は動いているplanを
138
- 最終活動の新しい順で上に、全工程が図から外れた完走planを古い順で下にまとめ、plan内は登録順です。
139
- 決着済みPhaseと図から外した工程は既定で畳み、開けば読めます。規約は
140
- [ADR 0067](docs/adr/0067-right-pane-shows-the-store-and-orders-by-activity.md)が正です。
141
-
142
- 静的工程表は`lattice todo gantt status`で`current / stale / missing`を確認でき、HTMLまたは
143
- digest付きsidecarの欠落・改ざんはtyped failureになります。
144
- dashboard daemonは起動時に読み込んだ版数をhealthで名乗り、installされた版と食い違えば`lattice status`の
145
- たびに新版daemonへ置き換わります。publishしただけで配信面が古いまま残ることはありません。
146
- 入れ替えは新daemonが登録済み全projectのstoreを読み終えるまで待つため、`lattice status`の応答が
147
- その間伸びます(実測: 8 project登録で約50秒台)。待ち時間は固定秒数ではなく、spawnした子が生きている
148
- 間だけ待ち、子が死ねば即座に`DASHBOARD_DAEMON_UNAVAILABLE`を返します。既定120秒の上限は、応答を
149
- 返さない子に対するbackstopであって正常な起動時間の見積りではありません。
150
- 状態を書き込む`start / block / unblock / done / evidence promote / reopen / revise / revise-phase / revise-set`
151
- では、監査actorとして次の3環境変数をすべて設定してください。
123
+ Full CLI surface: `lattice --help`, then
124
+ `lattice <plan|run|event|todo|sensor|bridge|runtime-errors> --help`.
125
+
126
+ ## Design principles
127
+
128
+ **The operating AI is part of the apparatus.** Lattice is driven by an AI agent, and that agent
129
+ is not outside the system — it is a component of it. So Lattice supplies only what the AI
130
+ *cannot* produce for itself: structure observation, contracts, verification, records, and
131
+ version boundaries. Estimation, judgment, and naming remain the AI's job. You will not find an
132
+ LLM call inside this product; adding one would duplicate a capability already present at the
133
+ point of use.
134
+
135
+ **Unknown is never rounded to "no conflict."** If a boundary was not verified, the verdict says
136
+ `missing`, not "independent." The absence of a dependency edge is not evidence of independence.
137
+
138
+ **Fail closed, and say why.** Every rejection carries a typed reason and a next action. A
139
+ transform that cannot be verified is not adopted. A finding that cannot be independently
140
+ re-derived is not recorded.
141
+
142
+ ## Patent
143
+
144
+ The design in this repository is the subject of a Japanese patent application:
145
+
146
+ | | |
147
+ |---|---|
148
+ | Application number | 特願2026-178950 (JP 2026-178950) |
149
+ | Filing date | 2026-07-27 |
150
+ | Title | 情報処理装置、ソフトウェア開発制御方法及びプログラム<br>(Information processing apparatus, software development control method, and program) |
151
+ | Claims | 12 |
152
+
153
+ **Patent rights are reserved.** Noncommercial use is licensed together with the software (see
154
+ [License](#license)). Commercial use is not granted — neither the copyright license nor the
155
+ patent license extends to it. The application is disclosed here so the rights position is
156
+ visible up front rather than discovered later.
157
+
158
+ ## Factory role
159
+
160
+ Lattice is one of the ten self-owned core products managed by the
161
+ [dotagents development factory](https://github.com/kitepon-rgb/dotagents). This repository owns
162
+ the plan/ToDo/run store, the bundled sensor, schemas, migrations, releases, and diagnostics;
163
+ dotagents owns cross-product installation and host integration.
164
+
165
+ - Product philosophy: [PLAN.md](PLAN.md)
166
+ - Public contract: [docs/00_product-contract.md](docs/00_product-contract.md)
167
+ - Immutable decisions: [docs/adr/](docs/adr/)
168
+ - Document map: [docs/README.md](docs/README.md)
169
+
170
+ ## Development
152
171
 
153
172
  ```bash
154
- export LATTICE_TODO_ACTOR_HOST=<host-id>
155
- export LATTICE_TODO_ACTOR_SESSION=<session-id>
156
- export LATTICE_TODO_ACTOR_AGENT=<agent-id>
173
+ npm test # product test gate
174
+ npm run check # syntax + control-character gate
175
+ npm run ci # full gate
157
176
  ```
158
177
 
159
- 不足またはidentifierとして不正な値がある場合、mutationはstoreを変更せず`ACTOR_UNRESOLVED`を返します。
160
- error detailの`missing_environment`/`invalid_environment`と
161
- `next_action: set_required_actor_environment_and_retry`を確認し、正規値を設定して同じ操作を再試行してください。
178
+ The full gate includes checks that are unusual and deliberate:
179
+
180
+ - **`check:cli-surface`** — every shipped command must have help text *and* be exercised through
181
+ a CLI entry point by a test. Shipping a command nobody ever ran is treated as a defect.
182
+ - **`check:open-questions`** — every unresolved question in an ADR must carry an explicit firing
183
+ condition, so "deferred" is never indistinguishable from "forgotten."
184
+ - **`check:reachability`** — every module must be reachable from a product entry point, or be
185
+ declared a research artifact with a reason.
186
+
187
+ Detailed operational notes (dashboard, bridge, actor environment, store transactions) are in
188
+ [README.ja.md](README.ja.md) and [docs/](docs/).
189
+
190
+ ## License
191
+
192
+ **[PolyForm Noncommercial License 1.0.0](LICENSE)** — free for noncommercial use.
193
+
194
+ - **Free:** personal projects, study and research, hobby and amateur work, charities,
195
+ educational institutions, public research organizations, and government institutions.
196
+ - **Not granted:** commercial use. That includes use inside a company's paid work or products,
197
+ regardless of whether Lattice itself is redistributed.
198
+
199
+ This is **not** an OSI-approved open source license, and that is deliberate: the design is
200
+ covered by a patent application and commercial rights are retained.
201
+
202
+ **For commercial use**, a separate license from the copyright and patent holder is required.
203
+ Enquiries can be made via [issues](https://github.com/kitepon-rgb/Lattice/issues). Whether a
204
+ license is granted, and on what terms, is decided case by case.
205
+
206
+ The bundled structure sensor in [`sensor/`](sensor/) is third-party work absorbed into this
207
+ repository and remains under the **MIT License**. Its upstream origin and attribution are
208
+ recorded in [`sensor/NOTICE`](sensor/NOTICE); the license text is
209
+ [`sensor/LICENSE`](sensor/LICENSE). The terms above do not modify it.
162
210
 
163
- 正確なargv、evidence descriptor、result wireは
164
- [ADR 0056](docs/adr/0056-todo-authoring-transitions.md)を参照してください。
211
+ © 2026 quolu (kitepon-rgb)
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.28.0",
4
- "description": "Lattice phase-aware TODO graph compiler and conflict-aware orchestration runtime",
3
+ "version": "0.30.0",
4
+ "description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
5
5
  "type": "module",
6
- "license": "MIT",
6
+ "license": "PolyForm-Noncommercial-1.0.0",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/kitepon-rgb/Lattice.git"
@@ -36,7 +36,7 @@
36
36
  "sensor/NOTICE"
37
37
  ],
38
38
  "publishConfig": {
39
- "access": "restricted"
39
+ "access": "public"
40
40
  },
41
41
  "dependencies": {
42
42
  "@clack/prompts": "^1.3.0",
@@ -65,5 +65,24 @@
65
65
  "check:cli-surface": "node scripts/verify-cli-surface.mjs",
66
66
  "check:open-questions": "node scripts/verify-open-questions.mjs",
67
67
  "check:reachability": "node scripts/verify-product-reachability.mjs"
68
- }
68
+ },
69
+ "homepage": "https://github.com/kitepon-rgb/Lattice#readme",
70
+ "bugs": {
71
+ "url": "https://github.com/kitepon-rgb/Lattice/issues"
72
+ },
73
+ "keywords": [
74
+ "ai",
75
+ "agent",
76
+ "multi-agent",
77
+ "claude",
78
+ "codex",
79
+ "orchestration",
80
+ "parallel",
81
+ "refactoring",
82
+ "code-analysis",
83
+ "dependency-graph",
84
+ "scheduling",
85
+ "cli",
86
+ "mcp"
87
+ ]
69
88
  }
@@ -92,9 +92,18 @@ async function readStrictJsonOnce(ref, label) {
92
92
  throw controlReadRace(`${label} changed during validation`);
93
93
  }
94
94
  const text = await handle.readFile('utf8');
95
- const after = await lstat(ref);
96
- if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size) {
97
- throw controlReadRace(`${label} changed during read`);
95
+ // `open`が成功した時点で、このfdは検証済みinodeを指している。書き手はtemp fileへ
96
+ // 書き切ってからrenameするので、そのinodeの内容は完結していて後から変わらない。
97
+ // つまり**読んでいる最中にpathが差し替わっても、読み取った内容は正しいsnapshotである**。
98
+ // ここでinodeの差し替えを失敗にすると、正しい読みを競合として捨てることになり、
99
+ // 書き手が連続publishしている間は何度再試行しても抜けられない(Linux CIで実測)。
100
+ //
101
+ // 一方、**同じinodeのまま**sizeが変わったのは、atomicでない書き込みか改変であり、
102
+ // 完結した内容を読んだ保証が無い。こちらは競合として再読する。
103
+ const after = await lstat(ref).catch(() => null);
104
+ if (after !== null && after.dev === opened.dev && after.ino === opened.ino
105
+ && after.size !== opened.size) {
106
+ throw controlReadRace(`${label} mutated in place during read`);
98
107
  }
99
108
  const errors = [];
100
109
  const tree = parseTree(text, errors, { allowTrailingComma: false, disallowComments: true });
@@ -117,8 +126,9 @@ async function readStrictJsonOnce(ref, label) {
117
126
  * 内容の異常ではなく再読で解ける競合であり、壊れたcontrol fileと同じerrorにしてはならない。
118
127
  *
119
128
  * 再試行するのは`CONTROL_READ_RACE`が付いた3条件だけである——検証中のinode/size変化、
120
- * 読み取り中のinodesize変化、最初のlstat後の消失。最大`CONTROL_READ_RETRY_LIMIT`回、
121
- * 間隔`CONTROL_READ_RETRY_DELAY_MS`で再読し、超えたら他と同じtyped errorで落とす。
129
+ * 読み取り中の同一inode内でのsize変化、最初のlstat後の消失。最大`CONTROL_READ_RETRY_LIMIT`回、
130
+ * `CONTROL_READ_RETRY_DELAY_MS`基準のjitter付きbackoffで再読し、超えたらtyped errorで落とす。
131
+ * **読み終えた後のinode差し替えは競合ではない**——読んだ内容は完結したsnapshotだからである。
122
132
  * JSON不正・schema不正・mode不正は競合ではないので一度も再試行せず即fail closedにする。
123
133
  */
124
134
  async function readStrictJson(ref, code, label) {
@@ -129,7 +139,9 @@ async function readStrictJson(ref, code, label) {
129
139
  if (error?.[CONTROL_READ_RACE] !== true || attempt >= CONTROL_READ_RETRY_LIMIT) {
130
140
  throw new BridgeConfigError(code, `${label} invalid`, undefined, error);
131
141
  }
132
- await new Promise((resolve) => setTimeout(resolve, CONTROL_READ_RETRY_DELAY_MS));
142
+ // 固定間隔だと、周期的に書き換える相手と歩調が揃って毎回衝突しうる。ばらつかせる。
143
+ const backoff = CONTROL_READ_RETRY_DELAY_MS * (attempt + 1) * (1 + Math.random());
144
+ await new Promise((resolve) => setTimeout(resolve, backoff));
133
145
  }
134
146
  }
135
147
  }