@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/LICENSE +148 -25
- package/README.ja.md +251 -0
- package/README.md +174 -127
- package/package.json +24 -5
- package/src/bridge-daemon.mjs +18 -6
- package/src/runtime-cli.mjs +285 -1
- package/src/runtime-control-store.mjs +30 -0
- package/src/runtime-decision-verifier.mjs +3 -0
- package/src/runtime-diff-observer.mjs +8 -1
- package/src/runtime-engine.mjs +5 -0
- package/src/runtime-io-sentinel.mjs +384 -0
- package/src/runtime-managed-supervisor.mjs +26 -25
- package/src/runtime-scripted-adapter-controller.mjs +15 -2
- package/src/runtime-scripted-worktree.mjs +104 -0
- package/src/runtime-socket-owner.mjs +125 -0
- package/src/todo-cli.mjs +14 -5
- package/src/todo-independence-guidance.mjs +62 -0
- package/src/todo-store.mjs +39 -0
- package/src/witness-scaffold.mjs +60 -11
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
|
-
|
|
4
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@quolu/lattice)
|
|
8
|
+
[](https://github.com/kitepon-rgb/Lattice/actions/workflows/ci.yml)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://nodejs.org/)
|
|
11
|
+
[](#patent)
|
|
5
12
|
|
|
6
|
-
|
|
13
|
+
**English** · [日本語](README.ja.md)
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
廃止済みruntimeや旧cache/dataへfallbackしません。Spotterはproject単位で生成stateの所有境界を守ります。
|
|
59
|
+
### The five acceptance conditions
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
A transform is adopted only when **all five** hold. One missing condition rejects it:
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
96
|
+
lattice status --json
|
|
63
97
|
```
|
|
64
98
|
|
|
65
|
-
`invalid
|
|
66
|
-
|
|
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
|
-
|
|
102
|
+
```bash
|
|
103
|
+
lattice sensor init . --json
|
|
104
|
+
```
|
|
70
105
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
77
|
-
lattice
|
|
78
|
-
lattice
|
|
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
|
-
|
|
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
|
|
107
|
-
lattice todo
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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": "
|
|
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": "
|
|
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
|
}
|
package/src/bridge-daemon.mjs
CHANGED
|
@@ -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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
*
|
|
121
|
-
*
|
|
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
|
-
|
|
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
|
}
|