@quolu/lattice 0.67.5 → 0.68.1
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.ja.md +62 -14
- package/README.md +60 -10
- package/docs/bridge-setup.md +32 -1
- package/package.json +6 -2
- package/src/bridge-cli.mjs +30 -0
- package/src/bridge-hub-heartbeat.mjs +9 -4
- package/src/dashboard-delivery.mjs +103 -0
- package/src/fs-publish.mjs +16 -0
- package/src/git-process.mjs +3 -1
- package/src/project-cli.mjs +12 -4
- package/src/runtime-os-observation.mjs +1 -1
- package/src/runtime-windows-process.mjs +1 -1
- package/src/todo-cli.mjs +20 -8
- package/src/todo-store.mjs +11 -4
package/README.ja.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/og.png" alt="Lattice — 閉ざされて見えた山岳地形から複数の進行経路が現れる" width="100%">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/kitepon/Lattice/main/.github/og.png" alt="Lattice — 閉ざされて見えた山岳地形から複数の進行経路が現れる" width="100%">
|
|
3
3
|
<br>
|
|
4
4
|
<sub><em>この画像は、閉ざされているように見えた場所から複数の進行経路が現れ、自律した実行者たちが協調して動き出す瞬間を表しています。</em></sub>
|
|
5
5
|
</p>
|
|
@@ -129,14 +129,49 @@ typed理由つきで分類します——宣言を直せば通るのか、操作
|
|
|
129
129
|
## 所有境界
|
|
130
130
|
|
|
131
131
|
本repoはplan/ToDo/run store、Lattice sensor、schema、migration、release、diagnosticsを
|
|
132
|
-
所有します。[dotagents](https://github.com/kitepon
|
|
133
|
-
|
|
132
|
+
所有します。[dotagents](https://github.com/kitepon/dotagents)はkitepon.devを支える内部toolchainとして、
|
|
133
|
+
任意の製品横断工場導入とhost統合を統括します。Latticeの製品状態、更新、診断、releaseを所有または
|
|
134
|
+
制御しません。
|
|
134
135
|
廃止済みの前身sensorは独立製品として配線せず、Lattice sensorだけを現役面とします。
|
|
135
136
|
MarkItDownは別区分の第三者CLIです。
|
|
136
137
|
|
|
137
138
|
現在の工程状態と完了証拠の正本は、このrepoのLattice storeです。文書の役割と現行導線は
|
|
138
|
-
[docs/README.md](docs/README.md)、製品思想は
|
|
139
|
-
[
|
|
139
|
+
[docs/README.md](https://github.com/kitepon/Lattice/blob/main/docs/README.md)、製品思想は
|
|
140
|
+
[PLAN.md](https://github.com/kitepon/Lattice/blob/main/PLAN.md)、公開contractは
|
|
141
|
+
[docs/00_product-contract.md](https://github.com/kitepon/Lattice/blob/main/docs/00_product-contract.md)を参照してください。
|
|
142
|
+
|
|
143
|
+
## 導入・更新・診断
|
|
144
|
+
|
|
145
|
+
npm packageが単独導入と更新の正規入口です。更新後はtyped discoveryでCLIを確認し、必要なら
|
|
146
|
+
製品所有のdashboardを新版へ入れ替えます。bridgeを有効化したhostだけ`bridge status`も確認します。
|
|
147
|
+
常駐bridgeは読み込み済みの旧版を1分以内に自動で置き換えます。
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm install -g @quolu/lattice@latest
|
|
151
|
+
lattice --version
|
|
152
|
+
lattice status --json
|
|
153
|
+
lattice bridge status --json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Release
|
|
157
|
+
|
|
158
|
+
Latticeのreleaseはこのrepoから行います。`package.json`と`CHANGELOG.md`を更新し、製品gateを通し、
|
|
159
|
+
release commitを既定branchへ着地・pushしてから、同じ版をregistryへ公開して実installを確認します。
|
|
160
|
+
`prepublishOnly`はdirty treeとremote既定branchへ未着地のcommitを拒否します。
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm run ci
|
|
164
|
+
npm run verify:release-commit
|
|
165
|
+
npm publish --access public
|
|
166
|
+
npm install -g @quolu/lattice@<version>
|
|
167
|
+
lattice --version
|
|
168
|
+
lattice status --json
|
|
169
|
+
lattice bridge status --json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
製品CI契約は[docs/ci-contract.md](https://github.com/kitepon/Lattice/blob/main/docs/ci-contract.md)、
|
|
173
|
+
dashboard/bridgeの配備固有確認は
|
|
174
|
+
[docs/operations/lattice-kitepon-deployment.md](https://github.com/kitepon/Lattice/blob/main/docs/operations/lattice-kitepon-deployment.md)が正です。
|
|
140
175
|
|
|
141
176
|
CLIの全体像は`lattice --help`、各公開namespaceの正規構文は
|
|
142
177
|
`lattice <plan|run|event|todo|sensor|factory-diagnostics|runtime-errors|bridge|hooks> --help`で確認できます。
|
|
@@ -145,12 +180,24 @@ CLIの全体像は`lattice --help`、各公開namespaceの正規構文は
|
|
|
145
180
|
|
|
146
181
|
## 開発
|
|
147
182
|
|
|
183
|
+
製品local gateは次の3入口です。
|
|
184
|
+
|
|
148
185
|
```bash
|
|
149
186
|
npm test
|
|
150
187
|
npm run check
|
|
151
188
|
npm run ci
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
製品localの保守で必要な時だけ次を使います。
|
|
192
|
+
|
|
193
|
+
```bash
|
|
152
194
|
node scripts/reap-orphan-test-daemons.mjs
|
|
153
195
|
lattice sensor sync . --json
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
次は任意の工場統合診断であり、Lattice単独の開発gateまたはrelease gateではありません。
|
|
199
|
+
|
|
200
|
+
```bash
|
|
154
201
|
spotter doctor
|
|
155
202
|
codex-sidecar diagnostics --project . --preset auditor --json
|
|
156
203
|
```
|
|
@@ -165,7 +212,7 @@ codex-sidecar diagnostics --project . --preset auditor --json
|
|
|
165
212
|
|
|
166
213
|
Node.js 22.13以上、ただし25.xを除きます(`engines: >=22.13 <25 || >=26`。Node 25.xはV8 turboshaft
|
|
167
214
|
WASM JITの不具合で同梱sensorが壊れるため、bannerを出して起動を止めます)。境界観測は配布物に同梱したLattice sensorだけを使い、PATH上の
|
|
168
|
-
廃止済みruntimeや旧cache/dataへfallbackしません。
|
|
215
|
+
廃止済みruntimeや旧cache/dataへfallbackしません。Latticeはproject単位で生成stateの所有境界を守ります。
|
|
169
216
|
|
|
170
217
|
どのrepoでも、Latticeの導入状態はdirectoryの有無を推測せず、最初に次のtyped discoveryで判定します。
|
|
171
218
|
|
|
@@ -188,7 +235,7 @@ lattice plan create --input .lattice/plan-create.json
|
|
|
188
235
|
|
|
189
236
|
`invalid`をMarkdown fallbackへ丸めず、`next_action`に従ってstoreを診断してください。
|
|
190
237
|
discoveryと初期transactionの不変条件は
|
|
191
|
-
[ADR 0058](docs/adr/0058-project-discovery-and-initial-authoring.md)が正です。
|
|
238
|
+
[ADR 0058](https://github.com/kitepon/Lattice/blob/main/docs/adr/0058-project-discovery-and-initial-authoring.md)が正です。
|
|
192
239
|
|
|
193
240
|
## 実行runを端から端まで動かす
|
|
194
241
|
|
|
@@ -257,7 +304,7 @@ required evidenceを束縛した`todo phase accept`で重監査の判断を記
|
|
|
257
304
|
「閉じた」の可視表現なので、監査の記録なしにそこへ行かせません。作成時に拒否はせず、
|
|
258
305
|
`todo migrate`/`plan create`の結果と最後のdoneのadvisoryで`terminal_audit_required`を通知します。
|
|
259
306
|
**終端監査はToDoのdispatch可否へ影響しません**(Phaseは重監査の順序だけを制御し、開始順はToDo DAGが決めます)。
|
|
260
|
-
規約は[ADR 0147](docs/adr/0147-audit-is-on-by-default.md)が正です。
|
|
307
|
+
規約は[ADR 0147](https://github.com/kitepon/Lattice/blob/main/docs/adr/0147-audit-is-on-by-default.md)が正です。
|
|
261
308
|
|
|
262
309
|
```bash
|
|
263
310
|
lattice todo phase status --plan <key> # phase無しplanでも暗黙Phaseを返す(implicit: true)
|
|
@@ -277,7 +324,8 @@ lattice todo phase baseline --reason <text> --except <監査したいplan_key>
|
|
|
277
324
|
一括の入口は、現在監査待ちで一度も監査に触れていないPhaseだけを対象にします。**自動では
|
|
278
325
|
実行しません**——どれを監査し、どれを歴史として畳むかは人が決め、Latticeは宣言を受け取って
|
|
279
326
|
記録するだけです。`--except`は「最近の作業でコードも生きているので本当に監査したい」planを
|
|
280
|
-
残すための口です。規約は
|
|
327
|
+
残すための口です。規約は
|
|
328
|
+
[ADR 0148](https://github.com/kitepon/Lattice/blob/main/docs/adr/0148-history-closes-unaudited-not-audited.md)が正です。
|
|
281
329
|
|
|
282
330
|
誤ってphase無しで作ったplanへ後からPhaseを被せる場合は、`revise-phase`の
|
|
283
331
|
`state_policy: acquire_phase`でdone状態を保ったまま獲得できます(未割当→割当の向きだけを許し、
|
|
@@ -310,13 +358,13 @@ LANや外部reverse proxyから閲覧するoptional bridgeは既定で無効で
|
|
|
310
358
|
完走したplanは図の場所を取りません。除いた工程は凡例の件数、右ペインの「全工程」一覧、各工程の詳細から
|
|
311
359
|
辿れ、詳細の前提・後続は除外前の依存関係を示します。総数・進捗・最長依存鎖は除外前の全工程で数えます。
|
|
312
360
|
凡例の件数バッジを押すと全工程を描いた図へ切り替わります。表示規約は
|
|
313
|
-
[ADR 0066](docs/adr/0066-gantt-live-scope-drops-finished-work.md)が正です。
|
|
361
|
+
[ADR 0066](https://github.com/kitepon/Lattice/blob/main/docs/adr/0066-gantt-live-scope-drops-finished-work.md)が正です。
|
|
314
362
|
|
|
315
363
|
右ペインは概要・選択工程・全工程の3面で、いずれもToDo storeを表示します(元plan Markdown本文は
|
|
316
364
|
再表示しません。元文書へは各工程の詳細が持つ行対応から辿ります)。全工程一覧は動いているplanを
|
|
317
365
|
最終活動の新しい順で上に、全工程が図から外れた完走planを古い順で下にまとめ、plan内は登録順です。
|
|
318
366
|
決着済みPhaseと図から外した工程は既定で畳み、開けば読めます。規約は
|
|
319
|
-
[ADR 0067](docs/adr/0067-right-pane-shows-the-store-and-orders-by-activity.md)が正です。
|
|
367
|
+
[ADR 0067](https://github.com/kitepon/Lattice/blob/main/docs/adr/0067-right-pane-shows-the-store-and-orders-by-activity.md)が正です。
|
|
320
368
|
|
|
321
369
|
`lattice todo gantt`と`lattice todo gantt status`による静的工程表は廃止済みで、呼ぶと
|
|
322
370
|
`STATIC_GANTT_RETIRED`が動的dashboardを案内します。
|
|
@@ -331,7 +379,7 @@ daemonの生死はdescriptor 1枚では持ちません。daemonは自分のpid
|
|
|
331
379
|
旧daemonが観測されないまま生き残ることはありません。descriptorだけを失った場合は2本目を建てず、
|
|
332
380
|
配信を続けているdaemonを引き取ります。signalを送るのはその場で再認証を通った相手だけで、応答しない
|
|
333
381
|
pidへは送りません(pid再利用で無関係のprocessを止めうるためです)。規約は
|
|
334
|
-
[ADR 0157](docs/adr/0157-dashboard-daemons-are-discoverable-by-record.md)が正です。
|
|
382
|
+
[ADR 0157](https://github.com/kitepon/Lattice/blob/main/docs/adr/0157-dashboard-daemons-are-discoverable-by-record.md)が正です。
|
|
335
383
|
状態を書き込む`start / block / unblock / done / evidence promote / reopen / note / independence mode /
|
|
336
384
|
revise / revise-phase / revise-set / phase review|accept|reject|reopen|close-unaudited|baseline /
|
|
337
385
|
dashboard adopt`では、監査actorとして次の3環境変数を渡せます。未設定ならhost/`session`/USERから
|
|
@@ -347,7 +395,7 @@ export LATTICE_TODO_ACTOR_AGENT=<agent-id>
|
|
|
347
395
|
blobを書きます。flagの順は問いません。空の設計メモは拒否します。無策なら`NO_PLAN`と書いてください。
|
|
348
396
|
|
|
349
397
|
authoring入口と閉じる口の契約は
|
|
350
|
-
[ADR 0181](docs/adr/0181-authoring-entry-accepts-drafts.md)を参照してください。
|
|
398
|
+
[ADR 0181](https://github.com/kitepon/Lattice/blob/main/docs/adr/0181-authoring-entry-accepts-drafts.md)を参照してください。
|
|
351
399
|
|
|
352
400
|
## 特許
|
|
353
401
|
|
|
@@ -380,4 +428,4 @@ authoring入口と閉じる口の契約は
|
|
|
380
428
|
**MIT License**のままです。upstreamの出自と帰属表示は[`sensor/NOTICE`](sensor/NOTICE)、
|
|
381
429
|
license本文は[`sensor/LICENSE`](sensor/LICENSE)が持ちます。上記の条件はこれを変更しません。
|
|
382
430
|
|
|
383
|
-
© 2026 quolu (kitepon
|
|
431
|
+
© 2026 quolu (kitepon.dev)
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/og.png" alt="Lattice — several viable routes emerging through an apparently blocked mountain valley" width="100%">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/kitepon/Lattice/main/.github/og.png" alt="Lattice — several viable routes emerging through an apparently blocked mountain valley" width="100%">
|
|
3
3
|
<br>
|
|
4
4
|
<sub><em>This image represents several viable paths emerging from terrain that first appeared blocked, as autonomous executors begin moving in coordination.</em></sub>
|
|
5
5
|
</p>
|
|
@@ -231,7 +231,7 @@ live dependency diagram refuses to fold such a plan away, because folding is how
|
|
|
231
231
|
says "closed", and nothing gets there without an evidence-bound `phase accept`. Creation is never
|
|
232
232
|
rejected over it; the requirement is reported instead. And the audit gate never touches dispatch:
|
|
233
233
|
phases order reviews, the ToDo DAG orders work
|
|
234
|
-
([ADR 0147](docs/adr/0147-audit-is-on-by-default.md)).
|
|
234
|
+
([ADR 0147](https://github.com/kitepon/Lattice/blob/main/docs/adr/0147-audit-is-on-by-default.md)).
|
|
235
235
|
|
|
236
236
|
**History closes unaudited, never audited.** Work that finished long ago cannot be audited — the
|
|
237
237
|
code under review has already moved. Demanding an audit there produces either a false finding
|
|
@@ -240,7 +240,7 @@ code under review has already moved. Demanding an audit there produces either a
|
|
|
240
240
|
incapable of passing as `accepted`** — phase-accept dependencies unlock on `accepted` alone. The
|
|
241
241
|
bulk entry point never runs by itself, and the machine never infers "old enough to skip"; a human
|
|
242
242
|
decides what gets audited and what becomes history
|
|
243
|
-
([ADR 0148](docs/adr/0148-history-closes-unaudited-not-audited.md)).
|
|
243
|
+
([ADR 0148](https://github.com/kitepon/Lattice/blob/main/docs/adr/0148-history-closes-unaudited-not-audited.md)).
|
|
244
244
|
|
|
245
245
|
## Patent
|
|
246
246
|
|
|
@@ -259,13 +259,48 @@ Noncommercial use is permitted under the [License](#license) below.
|
|
|
259
259
|
## Ownership boundary
|
|
260
260
|
|
|
261
261
|
This repository owns the plan/ToDo/run store, the bundled sensor, schemas, migrations,
|
|
262
|
-
releases, and diagnostics. [dotagents](https://github.com/kitepon
|
|
263
|
-
|
|
262
|
+
releases, and diagnostics. [dotagents](https://github.com/kitepon/dotagents) coordinates optional
|
|
263
|
+
cross-product factory installation and host integration; it does not own or control Lattice's
|
|
264
|
+
product state, update, diagnostics, or release.
|
|
264
265
|
|
|
265
|
-
- Product philosophy: [PLAN.md](PLAN.md)
|
|
266
|
-
- Public contract: [docs/00_product-contract.md](docs/00_product-contract.md)
|
|
267
|
-
- Immutable decisions: [docs/adr/](docs/adr
|
|
268
|
-
- Document map: [docs/README.md](docs/README.md)
|
|
266
|
+
- Product philosophy: [PLAN.md](https://github.com/kitepon/Lattice/blob/main/PLAN.md)
|
|
267
|
+
- Public contract: [docs/00_product-contract.md](https://github.com/kitepon/Lattice/blob/main/docs/00_product-contract.md)
|
|
268
|
+
- Immutable decisions: [docs/adr/](https://github.com/kitepon/Lattice/tree/main/docs/adr)
|
|
269
|
+
- Document map: [docs/README.md](https://github.com/kitepon/Lattice/blob/main/docs/README.md)
|
|
270
|
+
|
|
271
|
+
## Update and diagnostics
|
|
272
|
+
|
|
273
|
+
The npm package is the standalone update path. After updating, typed discovery verifies the
|
|
274
|
+
installed CLI and refreshes the product-owned dashboard when needed. Check the bridge only on a
|
|
275
|
+
host where it is enabled; its supervised process replaces an older loaded version within one minute.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
npm install -g @quolu/lattice@latest
|
|
279
|
+
lattice --version
|
|
280
|
+
lattice status --json
|
|
281
|
+
lattice bridge status --json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Release
|
|
285
|
+
|
|
286
|
+
Maintainers release Lattice from this repository. Update `package.json` and `CHANGELOG.md`, pass the
|
|
287
|
+
product gate, land and push the release commit on the default branch, then publish and verify that
|
|
288
|
+
same version from the registry. `prepublishOnly` rejects a dirty tree or a commit not landed on the
|
|
289
|
+
remote default branch.
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
npm run ci
|
|
293
|
+
npm run verify:release-commit
|
|
294
|
+
npm publish --access public
|
|
295
|
+
npm install -g @quolu/lattice@<version>
|
|
296
|
+
lattice --version
|
|
297
|
+
lattice status --json
|
|
298
|
+
lattice bridge status --json
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The product CI contract is [docs/ci-contract.md](https://github.com/kitepon/Lattice/blob/main/docs/ci-contract.md). The deployment-specific
|
|
302
|
+
dashboard and bridge checks are in
|
|
303
|
+
[docs/operations/lattice-kitepon-deployment.md](https://github.com/kitepon/Lattice/blob/main/docs/operations/lattice-kitepon-deployment.md).
|
|
269
304
|
|
|
270
305
|
## Development
|
|
271
306
|
|
|
@@ -275,6 +310,21 @@ npm run check # syntax + control-character gate
|
|
|
275
310
|
npm run ci # full gate
|
|
276
311
|
```
|
|
277
312
|
|
|
313
|
+
Product-local maintenance, when relevant:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
node scripts/reap-orphan-test-daemons.mjs
|
|
317
|
+
lattice sensor sync . --json
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
These optional factory-integration diagnostics are not Lattice's standalone development or release
|
|
321
|
+
gate:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
spotter doctor
|
|
325
|
+
codex-sidecar diagnostics --project . --preset auditor --json
|
|
326
|
+
```
|
|
327
|
+
|
|
278
328
|
The full gate includes checks that are unusual and deliberate:
|
|
279
329
|
|
|
280
330
|
- **`check:cli-surface`** — every shipped command must have help text *and* be exercised through
|
|
@@ -305,4 +355,4 @@ repository and remains under the **MIT License**. Its upstream origin and attrib
|
|
|
305
355
|
recorded in [`sensor/NOTICE`](sensor/NOTICE); the license text is
|
|
306
356
|
[`sensor/LICENSE`](sensor/LICENSE). The terms above do not modify it.
|
|
307
357
|
|
|
308
|
-
© 2026 quolu (kitepon
|
|
358
|
+
© 2026 quolu (kitepon.dev)
|
package/docs/bridge-setup.md
CHANGED
|
@@ -4,6 +4,37 @@ Latticeのproject dashboardは既定でloopbackだけにbindし、bridge用socke
|
|
|
4
4
|
LAN上のreverse proxyなどから閲覧する時だけ、利用者がlisten IPを明示してbridgeを有効化する。
|
|
5
5
|
`postinstall`では質問やnetwork公開を行わない。
|
|
6
6
|
|
|
7
|
+
## 工程操作から配信漏れを知る
|
|
8
|
+
|
|
9
|
+
`lattice status --json`とToDoの保存操作が、工程結果のstdoutに加え、stderrへ
|
|
10
|
+
`lattice.dashboard_delivery.v1`を返す。工程の保存成功を公開配信の成功と読み替えない。
|
|
11
|
+
|
|
12
|
+
| state | 確認できたこと |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `local_only` | PC内の工程表が利用可能。`reason`は`bridge_unconfigured`または`bridge_disabled` |
|
|
15
|
+
| `bridge_only` | bridge設定を利用。hubが未設定のため、その先の公開配信は未確認 |
|
|
16
|
+
| `hub_delivered` | hubが対象工程を受理し、一覧にonlineで掲載し、工程HTMLの中継が成功 |
|
|
17
|
+
| `failed` | 配信を確認できない。`reason`と`next_action`で原因と復旧入口を返す |
|
|
18
|
+
|
|
19
|
+
有効なbridge設定がある端末では、工程操作が常駐設定・実行体・実processを確認し、欠落や実行pathのずれを
|
|
20
|
+
正規の`bridge reconfigure`で復旧してから配信を確認する。無効・未設定のbridgeは自動公開しない。
|
|
21
|
+
PCの初期化等で設定自体が失われた時は`local_only`がその事実を示す。公開先を推測して復元はしない。
|
|
22
|
+
|
|
23
|
+
明示の復旧入口`lattice todo dashboard ensure --json`は、ローカルdaemonから設定済みbridgeとhub配信までを
|
|
24
|
+
処理する。結果は`lattice.todo_dashboard_ensure_result.v1`で、`activity.delivery`に同じ配信結果を含む。
|
|
25
|
+
配信失敗では非0を返す。通常の工程保存は配信故障で止めず、保存結果と配信失敗を別々に返す。
|
|
26
|
+
|
|
27
|
+
`bridge setup`/`reconfigure`にhubが設定されている場合は、dashboard起動、hubへの登録、一覧と工程HTMLの
|
|
28
|
+
確認までを成功条件とする。stdoutの`lattice.bridge_cli_result.v4`は維持し、stderrへ確認結果を返す。通信や中継が
|
|
29
|
+
失敗した時は`BRIDGE_HUB_DELIVERY_UNCONFIRMED`で非0を返す。通信故障でも保存済みの接続設定は保持する。
|
|
30
|
+
hub URLは端末から直接到達する登録先を使う。reverse proxy経由の登録は接続元がproxyになり、hubから端末への
|
|
31
|
+
経路を誤る。別の公開HTTPS入口がある構成では、公開HTTPSとSSEの受入も本書後半に従って実施する。
|
|
32
|
+
|
|
33
|
+
配信確認は全OS共通とし、常駐の復旧はMacのLaunchAgent、WindowsのStartup Folderの既存入口を使う。
|
|
34
|
+
実行中の版だけが異なる場合は既存の自動更新に任せ、配信確認から再設定を強制しない。
|
|
35
|
+
|
|
36
|
+
## 共通の設定入口
|
|
37
|
+
|
|
7
38
|
TTYでは`lattice bridge setup`で安全側既定の対話wizardを開始できる。最初の公開確認は既定で無効を選び、
|
|
8
39
|
cancelした場合は設定を変更しない。非TTYではhangせず、次の非対話commandを案内する。
|
|
9
40
|
portを省略するか`auto`にすると、49152–65535から候補を重複なく選び、
|
|
@@ -106,7 +137,7 @@ lattice bridge reconfigure --json
|
|
|
106
137
|
macOSのLaunchAgent載せ直しでは、`launchctl bootout`のexit 0はunload受付だけである。labelが
|
|
107
138
|
domainから消える(`launchctl print`が113)前に`bootstrap`すると、launchdは
|
|
108
139
|
`5 Input/output error`を返す。`reconfigure`はprint 113を待ってから載せる(0.60.7・
|
|
109
|
-
[ADR 0179](adr/0179-launchctl-bootout-completes-when-print-returns-113.md))。失敗時は
|
|
140
|
+
[ADR 0179](https://github.com/kitepon/Lattice/blob/main/docs/adr/0179-launchctl-bootout-completes-when-print-returns-113.md))。失敗時は
|
|
110
141
|
launchctlのexit codeとstderrが`lattice.cli_error.v2`の`detail`に残る。
|
|
111
142
|
|
|
112
143
|
### 公開面から自分のprojectが消えた時(`last_heartbeat`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.1",
|
|
4
4
|
"description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Quo / クオ at kitepon.dev",
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"unified": "^11.0.5",
|
|
72
72
|
"web-tree-sitter": "^0.25.3"
|
|
73
73
|
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"micromark-util-decode-string": "^2.0.1"
|
|
76
|
+
},
|
|
74
77
|
"scripts": {
|
|
75
78
|
"prepack": "npm --prefix sensor run build",
|
|
76
79
|
"verify:release-commit": "node scripts/verify-release-commit.mjs",
|
|
@@ -79,11 +82,12 @@
|
|
|
79
82
|
"test:profile": "node scripts/run-product-tests.mjs",
|
|
80
83
|
"test:sensor": "npm --prefix sensor test",
|
|
81
84
|
"check": "node scripts/check-syntax.mjs",
|
|
82
|
-
"ci": "npm run test && npm run test:sensor && npm run check && npm run check:cli-surface && npm run check:open-questions && npm run check:reachability && npm run verify:todo-store",
|
|
85
|
+
"ci": "npm run check:docs && npm run test && npm run test:sensor && npm run check && npm run check:cli-surface && npm run check:open-questions && npm run check:reachability && npm run verify:todo-store",
|
|
83
86
|
"verify:todo-store": "node bin/lattice.mjs todo verify --json",
|
|
84
87
|
"check:cli-surface": "node scripts/verify-cli-surface.mjs",
|
|
85
88
|
"check:open-questions": "node scripts/verify-open-questions.mjs",
|
|
86
89
|
"check:reachability": "node scripts/verify-product-reachability.mjs",
|
|
90
|
+
"check:docs": "node scripts/verify-docs.mjs && node scripts/verify-packed-markdown.mjs && node --test test/documentation-ci-contract.test.mjs test/markdown-link-targets.test.mjs",
|
|
87
91
|
"upstream:check": "node scripts/upstream-check.mjs",
|
|
88
92
|
"upstream:sync": "node scripts/upstream-sync.mjs"
|
|
89
93
|
},
|
package/src/bridge-cli.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import * as clack from '@clack/prompts';
|
|
|
6
6
|
import packageJson from '../package.json' with { type: 'json' };
|
|
7
7
|
import { resolveBridgeListenAddress } from './bridge-address.mjs';
|
|
8
8
|
import { registerBridgeUpstream } from './bridge-registrar.mjs';
|
|
9
|
+
import { ensureTodoDashboardDaemon } from './todo-dashboard-registry.mjs';
|
|
10
|
+
import { verifyBridgeHubDelivery } from './dashboard-delivery.mjs';
|
|
9
11
|
import {
|
|
10
12
|
BridgeConfigError, configureBridge, disableBridge, readBridgeConfig, restoreBridgeConfig,
|
|
11
13
|
normalizeBridgeAllowedHost, withBridgeOperationLock,
|
|
@@ -237,6 +239,26 @@ function result(action, config, recovery = null, liveness = null, diagnostics =
|
|
|
237
239
|
warnings };
|
|
238
240
|
}
|
|
239
241
|
|
|
242
|
+
/** 設定済みbridgeの常駐が欠けた場合だけ、正規の再設定経路で復旧する。 */
|
|
243
|
+
export async function recoverConfiguredBridge({ config, env = process.env,
|
|
244
|
+
launchAgent = platformLaunchAgent(), runtimeIdentity = readBridgeRuntimeIdentity,
|
|
245
|
+
reconfigure = runBridgeCli } = {}) {
|
|
246
|
+
const diagnostics = await bridgeDiagnostics({ config, launchAgent, env, runtimeIdentity });
|
|
247
|
+
if (diagnostics === null) return;
|
|
248
|
+
if (diagnostics.persistence.state === 'installed'
|
|
249
|
+
&& diagnostics.persistence.node_exists && diagnostics.persistence.bridge_exists
|
|
250
|
+
&& diagnostics.runtime?.state === 'running'
|
|
251
|
+
&& diagnostics.drift.every((field) => field === 'version')) return;
|
|
252
|
+
let errorOutput = '';
|
|
253
|
+
const code = await reconfigure({ argv: ['reconfigure', '--json'], env,
|
|
254
|
+
stdout: { write() {} }, stderr: { write(chunk) { errorOutput += chunk; } },
|
|
255
|
+
launchAgent, runtimeIdentity });
|
|
256
|
+
if (code !== 0) {
|
|
257
|
+
const error = JSON.parse(errorOutput);
|
|
258
|
+
throw new BridgeConfigError(error.code, error.message, error.detail);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
240
262
|
export async function collectBridgeSetupWizard({ input, output, prompts = clack } = {}) {
|
|
241
263
|
const common = { input, output };
|
|
242
264
|
const enabled = await prompts.confirm({ ...common,
|
|
@@ -287,6 +309,7 @@ export async function runBridgeCli({ argv, stdout, stderr, env = process.env,
|
|
|
287
309
|
stop: stopBridgeDaemon, clearStop: clearBridgeStopControl },
|
|
288
310
|
launchAgent = platformLaunchAgent(), runtimeIdentity = readBridgeRuntimeIdentity,
|
|
289
311
|
bridgePath = DEFAULT_BRIDGE_PATH,
|
|
312
|
+
ensureDashboard = ensureTodoDashboardDaemon, verifyDelivery = verifyBridgeHubDelivery,
|
|
290
313
|
prompts = clack, probe = probeBridgeListener, interfaces = networkInterfaces() } = {}) {
|
|
291
314
|
if (!Array.isArray(argv)) {
|
|
292
315
|
return fail(stderr, 'USAGE', 'usage: lattice bridge <setup|reconfigure|status|disable|register> [options] --json');
|
|
@@ -392,6 +415,7 @@ export async function runBridgeCli({ argv, stdout, stderr, env = process.env,
|
|
|
392
415
|
reuseCurrentPort: options.port === undefined,
|
|
393
416
|
});
|
|
394
417
|
try {
|
|
418
|
+
if (configured.hub !== null) await ensureDashboard({ env });
|
|
395
419
|
if (!agentSnapshot.loaded && current !== null) {
|
|
396
420
|
const requestStop = daemon.requestStop ?? daemon.stop ?? requestBridgeDaemonStop;
|
|
397
421
|
await requestStop({ env, listen: current.listen });
|
|
@@ -414,6 +438,12 @@ export async function runBridgeCli({ argv, stdout, stderr, env = process.env,
|
|
|
414
438
|
return configured;
|
|
415
439
|
});
|
|
416
440
|
} else return fail(stderr, 'USAGE', 'unknown bridge command or options');
|
|
441
|
+
// hubを指定した設定は、保存・常駐起動だけでは成功にしない。通信故障時も設定は
|
|
442
|
+
// 保持し、次の工程操作から同じ接続を復旧できるようにする。
|
|
443
|
+
if ((command === 'setup' || command === 'reconfigure') && config.hub !== null) {
|
|
444
|
+
const delivery = await verifyDelivery({ config, env });
|
|
445
|
+
stderr.write(`${JSON.stringify(delivery)}\n`);
|
|
446
|
+
}
|
|
417
447
|
// Liveness and the persistence/runtime diagnostics are only meaningful for
|
|
418
448
|
// a read: the mutating commands have just reconfigured the daemon and the
|
|
419
449
|
// socket may not have settled yet.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { randomBytes } from 'node:crypto';
|
|
19
19
|
import { hostname } from 'node:os';
|
|
20
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
20
|
+
import { link, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
|
|
23
23
|
import { bridgeConfigPaths } from './bridge-config.mjs';
|
|
@@ -68,13 +68,18 @@ export async function readOrCreateBridgeHubTerminalId({ env = process.env } = {}
|
|
|
68
68
|
if (error?.code !== 'ENOENT') throw error;
|
|
69
69
|
}
|
|
70
70
|
const terminalId = randomBytes(16).toString('hex');
|
|
71
|
+
const temporary = `${ref}.${terminalId}.tmp`;
|
|
72
|
+
await writeFile(temporary, `${JSON.stringify({ schema: TERMINAL_IDENTITY_SCHEMA, terminal_id: terminalId })}\n`,
|
|
73
|
+
{ encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
71
74
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
// 完成済みのbytesだけを、既存のIDを上書きせず公開する。
|
|
76
|
+
await link(temporary, ref);
|
|
74
77
|
return terminalId;
|
|
75
78
|
} catch (error) {
|
|
76
79
|
if (error?.code !== 'EEXIST') throw error;
|
|
77
|
-
//
|
|
80
|
+
// 同時生成の勝者が公開した完成済みIDを読む。
|
|
81
|
+
} finally {
|
|
82
|
+
await unlink(temporary);
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { BridgeConfigError, readBridgeConfig } from './bridge-config.mjs';
|
|
2
|
+
import { createBridgeHubHeartbeatController } from './bridge-hub-heartbeat.mjs';
|
|
3
|
+
|
|
4
|
+
// 配信結果は全OS共通。常駐の復旧はbridge CLIがOS固有の入口へ委ねる。
|
|
5
|
+
|
|
6
|
+
const SCHEMA = 'lattice.dashboard_delivery.v1';
|
|
7
|
+
const SETUP = 'lattice bridge setup --listen <IP> --dashboard --hub <URL> --json';
|
|
8
|
+
|
|
9
|
+
function receipt(state, reason, projectIds = [], urls = [], nextAction = null) {
|
|
10
|
+
return { schema: SCHEMA, state, reason, project_ids: projectIds, urls, next_action: nextAction };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function unconfirmed(reason, projectIds = []) {
|
|
14
|
+
return new BridgeConfigError('BRIDGE_HUB_DELIVERY_UNCONFIRMED',
|
|
15
|
+
'工程の公開配信を確認できません。接続設定は保存済みです。', {
|
|
16
|
+
reason, project_ids: projectIds, next_action: 'lattice todo dashboard ensure --json',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 登録の受付、一覧への掲載、工程HTMLの中継を同じ呼出しで確認する。 */
|
|
21
|
+
export async function verifyBridgeHubDelivery({ config, env = process.env, projectId = null,
|
|
22
|
+
heartbeat = async () => createBridgeHubHeartbeatController({ env }).tick({ config }),
|
|
23
|
+
fetchImpl = fetch, timeoutMs = 5_000 } = {}) {
|
|
24
|
+
if (config.hub === null) return receipt('bridge_only', 'hub_unconfigured', [], [], SETUP);
|
|
25
|
+
const delivered = await heartbeat();
|
|
26
|
+
const result = delivered?.result;
|
|
27
|
+
const registered = result?.registered;
|
|
28
|
+
if (!['accepted', 'partial'].includes(delivered?.state)
|
|
29
|
+
|| result?.schema !== 'lattice.bridge_hub_registration_result.v2'
|
|
30
|
+
|| !Array.isArray(registered) || !registered.every((id) => typeof id === 'string')
|
|
31
|
+
|| !Array.isArray(result.rejected)) {
|
|
32
|
+
throw unconfirmed(delivered?.state ?? 'heartbeat_missing');
|
|
33
|
+
}
|
|
34
|
+
const projectIds = projectId === null ? registered : [projectId];
|
|
35
|
+
if (projectIds.length === 0 || projectIds.some((id) => !registered.includes(id))
|
|
36
|
+
|| (projectId === null && result.rejected.length > 0)) {
|
|
37
|
+
throw unconfirmed('project_not_registered', projectIds);
|
|
38
|
+
}
|
|
39
|
+
const request = (url, accept) => fetchImpl(url, { redirect: 'error',
|
|
40
|
+
headers: { accept }, signal: AbortSignal.timeout(timeoutMs) });
|
|
41
|
+
let listing;
|
|
42
|
+
try {
|
|
43
|
+
const response = await request(new URL('projects/', config.hub.url), 'application/json');
|
|
44
|
+
if (response.status !== 200) throw unconfirmed('hub_listing_unavailable', projectIds);
|
|
45
|
+
listing = await response.json();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error?.code === 'BRIDGE_HUB_DELIVERY_UNCONFIRMED') throw error;
|
|
48
|
+
throw unconfirmed('hub_listing_unavailable', projectIds);
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(listing) || projectIds.some((id) =>
|
|
51
|
+
!listing.some((entry) => entry.project_id === id && entry.status === 'online'))) {
|
|
52
|
+
throw unconfirmed('project_not_visible', projectIds);
|
|
53
|
+
}
|
|
54
|
+
const urls = [];
|
|
55
|
+
for (const id of projectIds) {
|
|
56
|
+
const url = new URL(`projects/${encodeURIComponent(id)}/`, config.hub.url);
|
|
57
|
+
try {
|
|
58
|
+
const response = await request(url, 'text/html');
|
|
59
|
+
if (response.status !== 200 || !response.headers.get('content-type')?.includes('text/html')) {
|
|
60
|
+
throw unconfirmed('project_unreachable', [id]);
|
|
61
|
+
}
|
|
62
|
+
const html = await response.text();
|
|
63
|
+
if (!/<title>Lattice — [^<]+依存工程図<\/title>/u.test(html)) {
|
|
64
|
+
throw unconfirmed('project_response_invalid', [id]);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error?.code === 'BRIDGE_HUB_DELIVERY_UNCONFIRMED') throw error;
|
|
68
|
+
throw unconfirmed('project_unreachable', [id]);
|
|
69
|
+
}
|
|
70
|
+
urls.push(url.href);
|
|
71
|
+
}
|
|
72
|
+
return receipt('hub_delivered', null, projectIds, urls);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function ensureProjectDashboardDelivery({ projectId, env = process.env,
|
|
76
|
+
readConfig = readBridgeConfig, verify = verifyBridgeHubDelivery,
|
|
77
|
+
recover = async (options) => {
|
|
78
|
+
const { recoverConfiguredBridge } = await import('./bridge-cli.mjs');
|
|
79
|
+
await recoverConfiguredBridge(options);
|
|
80
|
+
} } = {}) {
|
|
81
|
+
const config = await readConfig({ env });
|
|
82
|
+
if (config === null || !config.enabled) {
|
|
83
|
+
return receipt('local_only', config === null ? 'bridge_unconfigured' : 'bridge_disabled',
|
|
84
|
+
[projectId], [], SETUP);
|
|
85
|
+
}
|
|
86
|
+
await recover({ config, env });
|
|
87
|
+
return verify({ config: await readConfig({ env }), env, projectId });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** stdoutの工程契約を変えず、保存と配信の結果を別のtyped receiptで伝える。 */
|
|
91
|
+
export async function reportProjectDashboardDelivery({ projectId, env, stderr,
|
|
92
|
+
ensureDelivery = ensureProjectDashboardDelivery }) {
|
|
93
|
+
let delivery;
|
|
94
|
+
try { delivery = await ensureDelivery({ projectId, env }); }
|
|
95
|
+
catch (error) {
|
|
96
|
+
// 外部境界の故障だけを配信失敗へ写す。内部の型・実装不整合は隠さない。
|
|
97
|
+
if (typeof error?.code !== 'string') throw error;
|
|
98
|
+
delivery = receipt('failed', error.code, [projectId], [],
|
|
99
|
+
error.detail?.next_action ?? 'lattice bridge status --json');
|
|
100
|
+
}
|
|
101
|
+
stderr?.write(`${JSON.stringify(delivery)}\n`);
|
|
102
|
+
return delivery;
|
|
103
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { rename } from 'node:fs/promises';
|
|
2
|
+
import { setTimeout } from 'node:timers/promises';
|
|
3
|
+
|
|
4
|
+
/** Windowsは読み手が開いている宛先へのrenameを一時的にEPERMで拒否する。
|
|
5
|
+
* 同じatomic renameを待って再実行し、恒久的な拒否は元のエラーで返す。 */
|
|
6
|
+
export async function renamePublishedFile(source, destination, {
|
|
7
|
+
renameFile = rename, platform = process.platform, wait = setTimeout, attempts = 8,
|
|
8
|
+
} = {}) {
|
|
9
|
+
for (let attempt = 0; ; attempt++) {
|
|
10
|
+
try { return await renameFile(source, destination); }
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (platform !== 'win32' || error?.code !== 'EPERM' || attempt + 1 >= attempts) throw error;
|
|
13
|
+
await wait(Math.min(64, 2 ** attempt));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/git-process.mjs
CHANGED
|
@@ -42,7 +42,9 @@ export function gitCatFileBatch(objectNames, { cwd, maxBodyBytes }) {
|
|
|
42
42
|
maxBuffer: (maxBodyBytes + 256) * objectNames.length,
|
|
43
43
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
44
44
|
});
|
|
45
|
-
|
|
45
|
+
// Linux / Node 24ではmaxBuffer超過時にもstatus=0と完全stdoutを返しつつ、errorだけを
|
|
46
|
+
// ENOBUFSにする競合がある。statusだけで成功判定するとprocess自身が報告した失敗を失う。
|
|
47
|
+
if (result.error || result.status !== 0 || result.stdout === undefined || result.stdout === null) {
|
|
46
48
|
throw new Error(`git cat-file --batch failed: status=${result.status ?? 'signal'}`);
|
|
47
49
|
}
|
|
48
50
|
const stdout = result.stdout;
|
package/src/project-cli.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { isTodoIndependenceLegacyMarker } from './todo-independence-contracts.mjs';
|
|
25
25
|
import { selectIndependenceGuidance } from './todo-independence-guidance.mjs';
|
|
26
26
|
import { ensureTodoDashboardActivity } from './todo-dashboard-registry.mjs';
|
|
27
|
+
import { reportProjectDashboardDelivery } from './dashboard-delivery.mjs';
|
|
27
28
|
import { resolveProjectIdentity } from './project-identity.mjs';
|
|
28
29
|
import {
|
|
29
30
|
buildTodoPlan,
|
|
@@ -336,8 +337,9 @@ async function resolveProjectState({ cwd, cliVersion, diagnoseStructureArtifacts
|
|
|
336
337
|
}
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
export async function runProjectStatus({ cwd, stdout, cliVersion, env = process.env,
|
|
340
|
-
ensureDashboardActivity = ensureTodoDashboardActivity
|
|
340
|
+
export async function runProjectStatus({ cwd, stdout, stderr = process.stderr, cliVersion, env = process.env,
|
|
341
|
+
ensureDashboardActivity = ensureTodoDashboardActivity,
|
|
342
|
+
reportDelivery = reportProjectDashboardDelivery }) {
|
|
341
343
|
const state = await resolveProjectState({
|
|
342
344
|
cwd, cliVersion, diagnoseStructureArtifacts: true,
|
|
343
345
|
});
|
|
@@ -349,13 +351,19 @@ export async function runProjectStatus({ cwd, stdout, cliVersion, env = process.
|
|
|
349
351
|
repoRoot: state.repoRoot, projectId: state.store.project_id, env,
|
|
350
352
|
});
|
|
351
353
|
const actorSession = env.LATTICE_TODO_ACTOR_SESSION;
|
|
352
|
-
await ensureDashboardActivity({
|
|
354
|
+
const activity = await ensureDashboardActivity({
|
|
353
355
|
repoRoot: state.repoRoot, projectId: state.store.project_id,
|
|
354
356
|
displayName: identity.displayName,
|
|
355
357
|
sessionId: isTodoIdentifier(actorSession) ? actorSession : `status-${process.pid}`, env,
|
|
356
358
|
});
|
|
357
|
-
|
|
359
|
+
if (activity !== undefined) {
|
|
360
|
+
await reportDelivery({ projectId: state.store.project_id, env, stderr });
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
358
363
|
// dashboardはdiscoveryの副作用。statusをdashboard故障で止めない(ADR 0181)。
|
|
364
|
+
stderr.write(`${JSON.stringify({ schema: 'lattice.dashboard_delivery.v1', state: 'failed',
|
|
365
|
+
reason: error.code ?? 'DASHBOARD_FAILED', project_ids: [state.store.project_id], urls: [],
|
|
366
|
+
next_action: 'lattice todo dashboard ensure --json' })}\n`);
|
|
359
367
|
}
|
|
360
368
|
}
|
|
361
369
|
stdout.write(`${JSON.stringify(state.result)}\n`);
|
|
@@ -16,7 +16,7 @@ export function osObservationEnvironment() { return { ...process.env, LC_ALL: 'C
|
|
|
16
16
|
|
|
17
17
|
/** processのstart identity(darwin/linuxはps lstart、win32はPowerShell StartTime Ticks)の生文字列を返す。 */
|
|
18
18
|
export async function observeStartIdentityRaw(pid) {
|
|
19
|
-
const executable = process.platform === 'win32' ? '
|
|
19
|
+
const executable = process.platform === 'win32' ? 'pwsh.exe' : '/bin/ps';
|
|
20
20
|
const args = process.platform === 'win32'
|
|
21
21
|
? ['-NoProfile', '-NonInteractive', '-Command',
|
|
22
22
|
`[System.Diagnostics.Process]::GetProcessById(${pid}).StartTime.ToUniversalTime().Ticks`]
|
|
@@ -37,7 +37,7 @@ export async function observeWindowsWorkerProcess(pid) {
|
|
|
37
37
|
}
|
|
38
38
|
const filter = JSON.stringify(`ProcessId=${pid}`);
|
|
39
39
|
const script = `Get-CimInstance Win32_Process -Filter ${filter} | Select-Object ProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress`;
|
|
40
|
-
const { stdout } = await execFileAsync('
|
|
40
|
+
const { stdout } = await execFileAsync('pwsh.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
41
41
|
encoding: 'utf8',
|
|
42
42
|
});
|
|
43
43
|
const row = JSON.parse(stdout.trim() || 'null');
|
package/src/todo-cli.mjs
CHANGED
|
@@ -3524,23 +3524,34 @@ function writesTodoStore(argv) {
|
|
|
3524
3524
|
}
|
|
3525
3525
|
}
|
|
3526
3526
|
|
|
3527
|
-
async function ensureActiveProjectDashboard({ repoRoot, env }) {
|
|
3527
|
+
async function ensureActiveProjectDashboard({ repoRoot, env, stderr, strict = false }) {
|
|
3528
3528
|
if (env.LATTICE_DASHBOARD_AUTOSTART === '0') return null;
|
|
3529
3529
|
let actor;
|
|
3530
|
-
try { actor = mutationActor(env); } catch { return null; }
|
|
3530
|
+
try { actor = mutationActor(env); } catch (error) { if (strict) throw error; return null; }
|
|
3531
3531
|
const sessionId = actor.session;
|
|
3532
3532
|
let store;
|
|
3533
3533
|
try { store = await readTodoStoreStable({ repoRoot }); }
|
|
3534
|
-
catch { return null; }
|
|
3534
|
+
catch (error) { if (strict) throw error; return null; }
|
|
3535
3535
|
let identity;
|
|
3536
3536
|
try { identity = await resolveProjectIdentity({ repoRoot, projectId: store.project_id, env }); }
|
|
3537
|
-
catch { return null; }
|
|
3537
|
+
catch (error) { if (strict) throw error; return null; }
|
|
3538
3538
|
try {
|
|
3539
|
-
|
|
3539
|
+
const activity = await ensureTodoDashboardActivity({
|
|
3540
3540
|
repoRoot, projectId: store.project_id, displayName: identity.displayName, sessionId, env,
|
|
3541
3541
|
});
|
|
3542
|
-
|
|
3542
|
+
const { reportProjectDashboardDelivery } = await import('./dashboard-delivery.mjs');
|
|
3543
|
+
const delivery = await reportProjectDashboardDelivery({ projectId: store.project_id, env, stderr });
|
|
3544
|
+
if (strict && delivery.state === 'failed') {
|
|
3545
|
+
throw new TodoStoreError('DASHBOARD_DELIVERY_FAILED', 'dashboard_delivery_failed', undefined,
|
|
3546
|
+
{ delivery });
|
|
3547
|
+
}
|
|
3548
|
+
return { ...activity, delivery };
|
|
3549
|
+
} catch (error) {
|
|
3543
3550
|
// dashboardは副作用。start/doneをdashboard故障で止めない(ADR 0181)。
|
|
3551
|
+
if (strict) throw error;
|
|
3552
|
+
stderr?.write(`${JSON.stringify({ schema: 'lattice.dashboard_delivery.v1', state: 'failed',
|
|
3553
|
+
reason: error.code ?? 'DASHBOARD_FAILED', project_ids: [store.project_id], urls: [],
|
|
3554
|
+
next_action: 'lattice todo dashboard ensure --json' })}\n`);
|
|
3544
3555
|
return null;
|
|
3545
3556
|
}
|
|
3546
3557
|
}
|
|
@@ -3744,7 +3755,8 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
3744
3755
|
// コマンドの副作用でしか daemon を ensure できず、OS再起動後に誰も書込まなければ
|
|
3745
3756
|
// 公開工程図が503のまま沈黙した。launchd等の周期実行から叩く冪等な入口。
|
|
3746
3757
|
action = async (repoRoot) => {
|
|
3747
|
-
const ensured = await ensureActiveProjectDashboard({ repoRoot,
|
|
3758
|
+
const ensured = await ensureActiveProjectDashboard({ repoRoot,
|
|
3759
|
+
env: { ...env, LATTICE_DASHBOARD_AUTOSTART: '1' }, stderr, strict: true });
|
|
3748
3760
|
return { schema: 'lattice.todo_dashboard_ensure_result.v1',
|
|
3749
3761
|
ensured: ensured !== null, activity: ensured };
|
|
3750
3762
|
};
|
|
@@ -4147,7 +4159,7 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
4147
4159
|
// `readTodoStoreStable` to read (2026-08-10 P0: `todo verify` was
|
|
4148
4160
|
// unreachable for exactly the case it exists to diagnose).
|
|
4149
4161
|
if (writesTodoStore(argv)) {
|
|
4150
|
-
await ensureActiveProjectDashboard({ repoRoot, env });
|
|
4162
|
+
await ensureActiveProjectDashboard({ repoRoot, env, stderr });
|
|
4151
4163
|
}
|
|
4152
4164
|
const result = atomicCommit
|
|
4153
4165
|
? await commitTodoStoreMutation({ repoRoot, argv, action, env })
|
package/src/todo-store.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { fsyncDirectory } from './fs-dir-sync.mjs';
|
|
3
|
+
import { renamePublishedFile } from './fs-publish.mjs';
|
|
3
4
|
import {
|
|
4
5
|
lstat, mkdir, open, readFile, readdir, realpath, rename, rm, rmdir,
|
|
5
6
|
} from 'node:fs/promises';
|
|
@@ -1210,7 +1211,10 @@ function readEvidenceBlob(absoluteRepo, oid) {
|
|
|
1210
1211
|
const [entry] = gitCatFileBatch([oid], {
|
|
1211
1212
|
cwd: absoluteRepo, maxBodyBytes: TODO_LIMITS.narrativeSectionBytes + 1,
|
|
1212
1213
|
});
|
|
1213
|
-
|
|
1214
|
+
// child_processのmaxBufferはprocess出力の総量を制限するだけで、証拠1件の意味上限ではない。
|
|
1215
|
+
// 契約上限はbytesで明示判定し、process bufferの打切りを意味判定に使わない。
|
|
1216
|
+
if (entry.missing || entry.type !== 'blob'
|
|
1217
|
+
|| entry.bytes.length > TODO_LIMITS.narrativeSectionBytes) throw new Error('not bounded blob');
|
|
1214
1218
|
if (evidenceBlobCache.size >= EVIDENCE_BLOB_CACHE_LIMIT) {
|
|
1215
1219
|
evidenceBlobCache.delete(evidenceBlobCache.keys().next().value);
|
|
1216
1220
|
}
|
|
@@ -1279,7 +1283,10 @@ function pinnedSourceLine(repoRoot, source, cache = null) {
|
|
|
1279
1283
|
}
|
|
1280
1284
|
if (blob === undefined) {
|
|
1281
1285
|
const blobEntry = entries.shift();
|
|
1282
|
-
|
|
1286
|
+
// commitとblobを同じbatchで読むと総buffer予算は2件分になる。source 1件の意味上限は
|
|
1287
|
+
// cacheへ入れる前に実bytesで判定する。
|
|
1288
|
+
if (blobEntry.missing || blobEntry.type !== 'blob'
|
|
1289
|
+
|| blobEntry.bytes.length > TODO_LIMITS.narrativeSectionBytes) throw new Error('not bounded source blob');
|
|
1283
1290
|
blob = blobEntry.bytes;
|
|
1284
1291
|
cache?.blobs.set(objectSpec, blob);
|
|
1285
1292
|
}
|
|
@@ -1716,7 +1723,7 @@ async function atomicWrite(absolute, bytes) {
|
|
|
1716
1723
|
process.stderr.write(`lattice: fsync skipped (WSL2/NTFS EIO) ${absolute}\n`);
|
|
1717
1724
|
}
|
|
1718
1725
|
await handle.close(); handle = null;
|
|
1719
|
-
await
|
|
1726
|
+
await renamePublishedFile(temporary, absolute);
|
|
1720
1727
|
await fsyncDirectory(path.dirname(absolute));
|
|
1721
1728
|
} finally {
|
|
1722
1729
|
if (handle) await handle.close();
|
|
@@ -1737,7 +1744,7 @@ async function atomicWriteMode(absolute, bytes, mode) {
|
|
|
1737
1744
|
process.stderr.write(`lattice: fsync skipped (WSL2/NTFS EIO) ${absolute}\n`);
|
|
1738
1745
|
}
|
|
1739
1746
|
await handle.close(); handle = null;
|
|
1740
|
-
await
|
|
1747
|
+
await renamePublishedFile(temporary, absolute);
|
|
1741
1748
|
await fsyncDirectory(path.dirname(absolute));
|
|
1742
1749
|
} finally {
|
|
1743
1750
|
if (handle) await handle.close();
|