@quolu/lattice 0.63.0 → 0.63.2

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.
Files changed (60) hide show
  1. package/LICENSE +147 -147
  2. package/README.ja.md +378 -378
  3. package/README.md +303 -303
  4. package/bin/lattice-mcp.mjs +0 -0
  5. package/bin/lattice-scripted-adapter.mjs +0 -0
  6. package/bin/lattice-scripted-worker.mjs +0 -0
  7. package/bin/lattice-work-order-adapter.mjs +0 -0
  8. package/bin/lattice.mjs +3 -1
  9. package/docs/bridge-setup.md +248 -248
  10. package/docs/schemas/lattice.executor_packet.v1.schema.json +57 -57
  11. package/docs/schemas/lattice.executor_receipt.v1.schema.json +66 -66
  12. package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +360 -360
  13. package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -56
  14. package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -72
  15. package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -81
  16. package/docs/schemas/lattice.plan_create_input.v4.schema.json +357 -85
  17. package/docs/schemas/lattice.plan_scope_review.v1.schema.json +55 -55
  18. package/docs/schemas/lattice.run_request.v1.schema.json +238 -238
  19. package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -55
  20. package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -78
  21. package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -86
  22. package/docs/schemas/lattice.todo_extraction.v2.schema.json +298 -298
  23. package/docs/schemas/lattice.todo_extraction.v3.schema.json +150 -150
  24. package/docs/schemas/lattice.todo_extraction.v4.schema.json +161 -161
  25. package/docs/schemas/lattice.todo_revision.v2.schema.json +260 -260
  26. package/docs/schemas/lattice.todo_revision_set.v3.schema.json +363 -363
  27. package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +47 -47
  28. package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +55 -55
  29. package/docs/schemas/lattice.todo_structure_set.v1.schema.json +264 -264
  30. package/package.json +109 -109
  31. package/sensor/LICENSE +21 -21
  32. package/sensor/NOTICE +19 -19
  33. package/sensor/dist/bin/lattice-sensor.js +9 -9
  34. package/sensor/dist/db/index.js +24 -24
  35. package/sensor/dist/db/migrations.js +41 -41
  36. package/sensor/dist/db/queries.js +164 -164
  37. package/sensor/dist/db/schema.sql +205 -205
  38. package/sensor/dist/directory.js +5 -5
  39. package/sensor/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  40. package/sensor/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  41. package/sensor/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  42. package/sensor/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  43. package/sensor/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  44. package/sensor/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  45. package/sensor/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  46. package/sensor/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  47. package/sensor/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  48. package/sensor/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  49. package/sensor/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  50. package/sensor/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  51. package/sensor/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  52. package/sensor/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  53. package/sensor/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  54. package/sensor/dist/mcp/liveness-watchdog.js +53 -53
  55. package/sensor/dist/mcp/server-instructions.js +95 -95
  56. package/sensor/package.json +56 -56
  57. package/src/project-cli.mjs +164 -24
  58. package/src/todo-authoring-input.mjs +35 -5
  59. package/src/todo-cli.mjs +2 -2
  60. package/src/todo-store.mjs +52 -21
@@ -1,248 +1,248 @@
1
- # Optional network bridge
2
-
3
- Latticeのproject dashboardは既定でloopbackだけにbindし、bridge用socketや設定を作らない。
4
- LAN上のreverse proxyなどから閲覧する時だけ、利用者がlisten IPを明示してbridgeを有効化する。
5
- `postinstall`では質問やnetwork公開を行わない。
6
-
7
- TTYでは`lattice bridge setup`で安全側既定の対話wizardを開始できる。最初の公開確認は既定で無効を選び、
8
- cancelした場合は設定を変更しない。非TTYではhangせず、次の非対話commandを案内する。
9
- portを省略するか`auto`にすると、49152–65535から候補を重複なく選び、
10
- 実際のexclusive bindとhealth確認に成功したportだけを保存する。
11
-
12
- ```bash
13
- lattice bridge setup --listen 192.168.1.50 --port auto --dashboard --allow-host lattice.example.com --json
14
- ```
15
-
16
- `--dashboard`は現在のlocal dashboard descriptorをrequestごとに解決するため、dashboard再起動でportが変わっても
17
- bridge設定はstaleにならない。固定upstreamを使う場合だけ`--upstream http://127.0.0.1:4318`を指定する。
18
- listen IPは常に許可Hostへ入り、reverse proxyで公開するhostnameは`--allow-host`を反復して追加する。
19
- 許可されていないHostは421となるため、DNS rebinding originへ工程情報を返さない。
20
-
21
- ```bash
22
- lattice bridge status --json
23
- lattice bridge reconfigure --listen 192.168.1.50 --port auto --dashboard --json
24
- lattice bridge disable --json
25
- ```
26
-
27
- 設定は`~/.lattice/bridge.json`へmode 0600でatomic保存する。`setup`/`reconfigure`は実bridge daemonが
28
- 選択socketをexclusive bindしhealthを返すまで成功にしない。起動に失敗した場合は旧設定へ戻す。
29
- `disable`もbridge socketの停止確認後に成功し、loopbackのlocal dashboardは停止しない。
30
- configまたはdaemon descriptorが壊れている場合、`disable`は公開socketのfail-closed停止を優先して破損control
31
- fileを除去し、JSON結果の`recovery`へ処置を明示する。その後は`setup`で再設定できる。
32
-
33
- 自動化・隔離testではabsoluteな`LATTICE_CONFIG_DIR`で設定rootを変更できる。無効な設定、低いport、
34
- 使用中の明示port、危険なrequest target、到達不能upstreamはsilent fallbackせずtyped errorを返す。
35
-
36
- ## 常駐が黙って死んでいないか確かめる
37
-
38
- `reachable`は「設定したaddressで誰かが応答しているか」しか答えない。常駐設定(macOSのLaunchAgent、
39
- WindowsのStartup launcher)が消えたbinaryを指していると、supervisorは起動できないprocessを回し続け、
40
- どこにもエラーが出ないまま公開面から端末だけが消える。`status`はそれを1回で名指しする。
41
-
42
- ```bash
43
- lattice bridge status --json
44
- ```
45
-
46
- bridgeが無効な間は以下すべてnullで、bridgeを有効にしている時だけ観測する。
47
-
48
- **`persistence`** — 常駐設定が実際に起動する対象。
49
-
50
- | field | 意味 |
51
- | --- | --- |
52
- | `state` | `installed`/`not_installed`/`unreadable` |
53
- | `loaded` | launchdへ読み込み済みか。Windowsには対応概念が無いのでnull |
54
- | `node_path`・`node_exists` | 起動するNode実行体と、それが今も存在するか |
55
- | `bridge_path`・`bridge_exists` | 起動するbridge scriptと、それが今も存在するか |
56
- | `error` | `unreadable`のときだけtyped code(例`BRIDGE_LAUNCH_AGENT_PLIST_UNSAFE`) |
57
- | `error=BRIDGE_PERSISTENCE_STATE_SPLIT` | launcherまたはplistの片割れだけが残った状態。手でfileを消さず、`lattice bridge reconfigure --json`で常駐設定を揃える(0.57.2以降) |
58
-
59
- `BRIDGE_PERSISTENCE_STATE_SPLIT`は、常駐設定の一方(macOSのplist、またはWindowsのlauncher/descriptor)だけが
60
- 存在することを示す。これは復旧対象そのものが壊れている状態なので、手作業でfileを削除せず、
61
- `lattice bridge reconfigure --json`を実行して正規のinstall経路で両方を再生成する。
62
-
63
- **`runtime`** — いま応答しているprocess自身の申告。`state`は`running`/`not_running`/`unattested`/
64
- `descriptor_invalid`で、`running`以外では各値がnullになる。`running`でも、identityを返さない
65
- 0.55.0より前のdaemonが走っている間は`version`以下がnullになる(この場合`runtime_drift`は空になり、
66
- 乖離の有無は判定できていない——「乖離なし」ではない)。
67
-
68
- `reconfigure`直後は、identityの確認requestが400msで打ち切られるため一時的に`unattested`を返すことが
69
- ある(daemonの起動直後と競合する)。数秒おいて引き直せば`running`になる。続くようなら本物の不整合で、
70
- `reachable`がtrueでも公開面は認証できていない。
71
-
72
- | field | 意味 |
73
- | --- | --- |
74
- | `pid` | 応答しているprocessのpid |
75
- | `version` | そのprocessが読み込んでいるLatticeの版 |
76
- | `node_path`・`node_version` | そのprocessを実行しているNodeの実体pathと版 |
77
- | `bridge_path` | そのprocessが実行しているbridge script |
78
-
79
- **`runtime_drift`** — 両者の食い違い。空配列は「差が無い」または「`runtime`が名乗っていないので
80
- 判定できない」のどちらかである。
81
-
82
- | 値 | 意味 |
83
- | --- | --- |
84
- | `bridge_path` | 常駐設定と違うtreeのcodeが走っている(開発treeの残骸など) |
85
- | `node_path` | 常駐設定が指すnodeと実走nodeが別実体。焼くのは意図的にaliasなので、比較はrealpathで行う |
86
- | `version` | npm更新後まだ旧moduleを保持している |
87
-
88
- **`remedy`** — 自己解消しない状態にだけ、打つべきコマンドが入る。出るのは次の4つ。
89
-
90
- - `persistence.node_exists`または`bridge_exists`がfalse(起動対象が消えた)
91
- - `persistence.state`が`not_installed`(bridgeは有効なのに常駐設定が無い。いま走っているdaemonが
92
- 最後の1つで、再起動しても戻らない)
93
- - `persistence.state`が`unreadable`(常駐設定を読めない)
94
- - `runtime_drift`に`node_path`または`bridge_path`がある
95
-
96
- `version`だけの差には`remedy`を出さない。daemonは60秒ごとにon-diskのpackage.jsonと自分の版を
97
- 突き合わせ、差があれば自ら終了してsupervisorに新codeで起動し直させる。放っておいて最大1分ほどで
98
- 解消するので、コマンドを出す状態ではない。自己解消する差にコマンドを出すと、本物の障害が埋もれる。
99
-
100
- `remedy`が出たら`reconfigure`で作り直す。plistやlauncherを手で書き換えない。
101
-
102
- ```bash
103
- lattice bridge reconfigure --json
104
- ```
105
-
106
- macOSのLaunchAgent載せ直しでは、`launchctl bootout`のexit 0はunload受付だけである。labelが
107
- domainから消える(`launchctl print`が113)前に`bootstrap`すると、launchdは
108
- `5 Input/output error`を返す。`reconfigure`はprint 113を待ってから載せる(0.60.7・
109
- [ADR 0179](adr/0179-launchctl-bootout-completes-when-print-returns-113.md))。失敗時は
110
- launchctlのexit codeとstderrが`lattice.cli_error.v2`の`detail`に残る。
111
-
112
- ### 公開面から自分のprojectが消えた時(`last_heartbeat`)
113
-
114
- hubへ繋いでいる端末では、`runtime.last_heartbeat`が最後にhubへ名乗った結果を持つ。公開一覧で
115
- 自分のprojectがofflineになっている時、原因が端末側か配線側かはここで分かれる。
116
-
117
- | `state` | 意味 | 打つ手 |
118
- | --- | --- | --- |
119
- | `accepted` | 全件受理された。公開面に出ていないならhub側の可視性設定を見る | — |
120
- | `partial` | 一部が他の生きた端末に所有されている。`rejected_projects`が名指しする | 意図した端末なら放置。奪うなら`adopt` |
121
- | `rejected` | hubがrequest全体を拒否した。`detail`にtyped code | detailのcodeで分岐 |
122
- | `unreachable` | hubへ届かない。配線かhubの停止 | hubのURLと生死を確認 |
123
- | `skipped_no_projects` | 配信しているprojectが0件。名乗るものが無い | 正常な静止。公開したいならそのrepoで作業する |
124
- | `skipped_no_dashboard` | dashboard daemonを観測できない。配信そのものが立っていない | daemonの生死を見る。`todo`系commandを1回打てば起動する |
125
- | `null` | hub未設定、またはまだ1回も送っていない | — |
126
-
127
- 名乗る集合はdaemonが実際に配信している集合そのもの(ADR 0165)。配信集合は
128
- `last_seen_at`が1週間以内、またはactive run、または監査待ちPhaseがあるprojectである。
129
- 人がCLIを叩かなくても、その条件を満たす限り公開面に残る。1週間を超え、runも監査待ちも
130
- 無いprojectは配信から外れる。heartbeatの90秒TTLは、配信そのものが止まった時
131
- (daemon停止)にofflineへ落とすためのもので、鮮度窓ではない。
132
-
133
- ### 焼き込むnode pathの選び方(ADR 0163)
134
-
135
- 常駐設定へ焼くnodeのpathは、版付きの実体(homebrewの`Cellar/node/<version>/bin/node`、nvm-windowsの
136
- 版ディレクトリ)ではなく、**同じbinaryを指すとrealpathで検証できた安定alias**(`/opt/homebrew/bin/node`、
137
- `C:\Program Files\nodejs\node.exe`)を選ぶ。`brew upgrade node`が旧versionのディレクトリごと消しても
138
- 起動対象が残るようにするためである。
139
-
140
- 安定aliasを検証できない環境(shim方式のasdf/volta等。shimは自身のlauncherへ解決されるので実体と
141
- 一致しない)では、版付きpathのまま焼く。検証していないpathを推測で焼けば別のnodeでdaemonが起動して
142
- しまうためで、そこでの防御は起動継続ではなく`node_exists`による消滅の可視化である。
143
-
144
- > **0.55.0より前に設定した常駐は自動では移行しない。** 焼き直しは`reconfigure`を実行した時にだけ
145
- > 起きるので、Latticeを更新しただけの端末は版付きpathを抱えたままになる。更新後に各端末で
146
- > `lattice bridge reconfigure --json`を1回打つ。現在どちらを焼いているかは`persistence.node_path`で読める。
147
-
148
- なお`setup`/`reconfigure`をnode_modules配下でない実体(開発tree)から実行すると、結果の`warnings`へ
149
- `BRIDGE_PERSISTED_FROM_DEVELOPMENT_TREE`が入る(該当しなければ空配列)。そのtreeを動かすと常駐が
150
- 止まり、`npm`更新も反映されない。開発treeから常駐させること自体は正当な操作なので拒否はしない。
151
-
152
- ## listen IPがDHCPで動く場合
153
-
154
- 設定したlisten IPがホストから消えると、古いsocketは死んだアドレスへ取り残され、LANから到達できなくなる。
155
- daemonは各reconcileで実効アドレスを解決し直し、同一subnet(IPv4 /24、IPv6 /64)に生きたアドレスがあれば
156
- そこへbindし直す。VPNや別NICなど異なるnetworkのアドレスは採用せず、候補が無ければ
157
- `BRIDGE_LISTEN_ADDRESS_ABSENT`で公開socketをfail-closedにする。再bind先は許可Hostへ自動で加わる。
158
-
159
- `LATTICE_BRIDGE_REGISTRAR_SSH_HOST`と`LATTICE_BRIDGE_REGISTRAR_SCRIPT`を両方設定すると、新しいbindingを
160
- 張るたびに`ssh <host> <script> <port>`でreverse proxy hostへ自己登録する。アドレスは送らず、remote側が
161
- ssh送信元から決めるため、各hostは自分自身しか登録できない。登録の失敗はbridgeを落とさずstderrへ
162
- typedに報告する。この配線が無いと、Caddy等が持つリテラルはlease変更のたびに黙って陳腐化する。
163
-
164
- ## reverse proxyへ逆トンネルで繋ぐ(LAN bindを使わない)
165
-
166
- reverse proxy hostへsshで到達できるなら、LANへbindせずloopbackだけで公開できる。bridgeが動くhostから
167
- 接続しに行くため、そのhostのLAN addressはreverse proxyのどこにも現れず、追従も自己登録も不要になる。
168
-
169
- ```bash
170
- lattice bridge setup --listen 127.0.0.1 --port 53939 --dashboard --allow-host lattice.example.com --json
171
- ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \
172
- -R 172.18.0.1:53939:127.0.0.1:53939 proxy-host
173
- ```
174
-
175
- reverse proxyはこの固定endpointだけを見る。転送口のbind先は、reverse proxyが到達できるaddressにする。
176
- Docker上のreverse proxyでは、containerの`127.0.0.1`はcontainer自身のloopbackでありhostのそれではないため、
177
- hostのloopbackへ開いた口には届かない。対象networkのgateway(`docker network inspect`の`Gateway`)へbindする。
178
-
179
- sshdは既定の`GatewayPorts no`だと`127.0.0.1`にしかbindできない。`clientspecified`にすると、clientが明示した
180
- addressだけにbindする(`yes`と違い全interfaceへは晒さない)。host firewallがINPUTをDROPしている場合は、
181
- その1 portだけを許可する。`ExitOnForwardFailure=yes`は、転送口を開けないまま接続だけ生かす状態を防ぐ。
182
- 常駐はprocess supervisorのKeepAliveに任せ、切断時は張り直す。
183
-
184
- ## Docker Caddy/Cloudflare Tunnelへ接続する
185
-
186
- bridgeを有効化したMacとreverse proxy hostの間で、まず許可Hostを付けたLAN到達を確認する。
187
- この段階が失敗している時はDNSやTunnelを追加しない。
188
-
189
- ```bash
190
- curl --fail --header 'Host: lattice.example.com' \
191
- http://MAC_LAN_IP:BRIDGE_PORT/projects/
192
- ```
193
-
194
- Caddyは既存のDocker networkと証明書運用を維持し、Lattice用siteだけを追加する。
195
-
196
- ```caddyfile
197
- lattice.example.com {
198
- reverse_proxy MAC_LAN_IP:BRIDGE_PORT {
199
- flush_interval -1
200
- }
201
- }
202
- ```
203
-
204
- 本番反映はcontainer内で`caddy validate`を通してから`caddy reload`する。Caddyfileを単一ファイルで
205
- bind mountしている構成では、atomic renameでhost側fileを置換するとcontainerが旧inodeを参照し続ける。
206
- 更新前backupを残し、inodeを維持するin-place更新を使うか、directory bind mountへ変更する。
207
-
208
- remote-managed Cloudflare Tunnelでは、Tunnel実行tokenを設定APIの代用にしない。Cloudflareの正規管理面で
209
- public hostname `lattice.kitepon.dev` を次のoriginへ対応付ける。
210
-
211
- - Service: `https://caddy:443`
212
- - TLS Origin Server Name: `lattice.kitepon.dev`(管理面に同等の設定がある場合は`Match SNI to Host`でもよい)
213
-
214
- `http://caddy:80`は選ばない。CaddyのHTTPからHTTPSへのredirectをTunnelがorigin応答として返す構成は、
215
- 外部requestが同じ公開URLへ戻るredirect loopになり得るためである。外部gateではredirectを追って200にせず、
216
- 最初の応答がHTTPSの200であることを確認する。
217
-
218
- 受入は次の3 gateを独立して記録し、後段の成功で前段を代用しない。
219
-
220
- 1. **LAN bridge**: reverse proxy hostから許可Host付きで`http://MAC_LAN_IP:BRIDGE_PORT/projects/`が200。
221
- 2. **Docker Caddy**: Caddyへ`Host: lattice.kitepon.dev`を付けたHTTPS requestが200。証明書検証を省略する
222
- 内部probeを外部公開成功の証拠にはしない。
223
- 3. **Cloudflare public HTTPS**: `https://lattice.kitepon.dev/projects/`がredirectなしで200となり、一覧から開いた
224
- `/projects/<project_id>/`のHTML titleが`Lattice — <project名> 依存工程図`である。
225
-
226
- 公開viewerの404も、ブラウザとAPIの両契約を別々に確認する。未知URLへ`Accept: text/html`を
227
- 付けたrequestはHTTP 404かつ`Content-Type: text/html`で、`noindex, nofollow`と
228
- `/projects/`、`https://kitepon.dev/`への戻り先を持つ。`Accept: application/json`では
229
- HTTP 404かつ`Content-Type: application/json`で、既存の
230
- `lattice.todo_gantt_http_error.v1`を返す。
231
-
232
- ```bash
233
- curl --silent --show-error --include --header 'Accept: text/html' \
234
- https://lattice.kitepon.dev/unknown
235
- curl --silent --show-error --include --header 'Accept: application/json' \
236
- https://lattice.kitepon.dev/unknown
237
- ```
238
-
239
- 外部gateはHTMLだけで閉じず、各projectの
240
- `https://lattice.kitepon.dev/projects/<project_id>/events`も確認する。応答は200かつ
241
- `Content-Type: text/event-stream`で、接続直後に`event: state`と現在の`head_digest`を返さなければならない。
242
- 接続を開いたまま正規のTodo更新を行い、新しい`state`が同じstreamへ届くことを確認する。切断後に再接続しても
243
- 再び初回`state`が届き、そのdigestが最新headと一致することまでを継続・再接続gateとする。
244
-
245
- ```bash
246
- curl --fail --show-error --include --no-buffer --max-time 15 \
247
- https://lattice.kitepon.dev/projects/PROJECT_ID/events
248
- ```
1
+ # Optional network bridge
2
+
3
+ Latticeのproject dashboardは既定でloopbackだけにbindし、bridge用socketや設定を作らない。
4
+ LAN上のreverse proxyなどから閲覧する時だけ、利用者がlisten IPを明示してbridgeを有効化する。
5
+ `postinstall`では質問やnetwork公開を行わない。
6
+
7
+ TTYでは`lattice bridge setup`で安全側既定の対話wizardを開始できる。最初の公開確認は既定で無効を選び、
8
+ cancelした場合は設定を変更しない。非TTYではhangせず、次の非対話commandを案内する。
9
+ portを省略するか`auto`にすると、49152–65535から候補を重複なく選び、
10
+ 実際のexclusive bindとhealth確認に成功したportだけを保存する。
11
+
12
+ ```bash
13
+ lattice bridge setup --listen 192.168.1.50 --port auto --dashboard --allow-host lattice.example.com --json
14
+ ```
15
+
16
+ `--dashboard`は現在のlocal dashboard descriptorをrequestごとに解決するため、dashboard再起動でportが変わっても
17
+ bridge設定はstaleにならない。固定upstreamを使う場合だけ`--upstream http://127.0.0.1:4318`を指定する。
18
+ listen IPは常に許可Hostへ入り、reverse proxyで公開するhostnameは`--allow-host`を反復して追加する。
19
+ 許可されていないHostは421となるため、DNS rebinding originへ工程情報を返さない。
20
+
21
+ ```bash
22
+ lattice bridge status --json
23
+ lattice bridge reconfigure --listen 192.168.1.50 --port auto --dashboard --json
24
+ lattice bridge disable --json
25
+ ```
26
+
27
+ 設定は`~/.lattice/bridge.json`へmode 0600でatomic保存する。`setup`/`reconfigure`は実bridge daemonが
28
+ 選択socketをexclusive bindしhealthを返すまで成功にしない。起動に失敗した場合は旧設定へ戻す。
29
+ `disable`もbridge socketの停止確認後に成功し、loopbackのlocal dashboardは停止しない。
30
+ configまたはdaemon descriptorが壊れている場合、`disable`は公開socketのfail-closed停止を優先して破損control
31
+ fileを除去し、JSON結果の`recovery`へ処置を明示する。その後は`setup`で再設定できる。
32
+
33
+ 自動化・隔離testではabsoluteな`LATTICE_CONFIG_DIR`で設定rootを変更できる。無効な設定、低いport、
34
+ 使用中の明示port、危険なrequest target、到達不能upstreamはsilent fallbackせずtyped errorを返す。
35
+
36
+ ## 常駐が黙って死んでいないか確かめる
37
+
38
+ `reachable`は「設定したaddressで誰かが応答しているか」しか答えない。常駐設定(macOSのLaunchAgent、
39
+ WindowsのStartup launcher)が消えたbinaryを指していると、supervisorは起動できないprocessを回し続け、
40
+ どこにもエラーが出ないまま公開面から端末だけが消える。`status`はそれを1回で名指しする。
41
+
42
+ ```bash
43
+ lattice bridge status --json
44
+ ```
45
+
46
+ bridgeが無効な間は以下すべてnullで、bridgeを有効にしている時だけ観測する。
47
+
48
+ **`persistence`** — 常駐設定が実際に起動する対象。
49
+
50
+ | field | 意味 |
51
+ | --- | --- |
52
+ | `state` | `installed`/`not_installed`/`unreadable` |
53
+ | `loaded` | launchdへ読み込み済みか。Windowsには対応概念が無いのでnull |
54
+ | `node_path`・`node_exists` | 起動するNode実行体と、それが今も存在するか |
55
+ | `bridge_path`・`bridge_exists` | 起動するbridge scriptと、それが今も存在するか |
56
+ | `error` | `unreadable`のときだけtyped code(例`BRIDGE_LAUNCH_AGENT_PLIST_UNSAFE`) |
57
+ | `error=BRIDGE_PERSISTENCE_STATE_SPLIT` | launcherまたはplistの片割れだけが残った状態。手でfileを消さず、`lattice bridge reconfigure --json`で常駐設定を揃える(0.57.2以降) |
58
+
59
+ `BRIDGE_PERSISTENCE_STATE_SPLIT`は、常駐設定の一方(macOSのplist、またはWindowsのlauncher/descriptor)だけが
60
+ 存在することを示す。これは復旧対象そのものが壊れている状態なので、手作業でfileを削除せず、
61
+ `lattice bridge reconfigure --json`を実行して正規のinstall経路で両方を再生成する。
62
+
63
+ **`runtime`** — いま応答しているprocess自身の申告。`state`は`running`/`not_running`/`unattested`/
64
+ `descriptor_invalid`で、`running`以外では各値がnullになる。`running`でも、identityを返さない
65
+ 0.55.0より前のdaemonが走っている間は`version`以下がnullになる(この場合`runtime_drift`は空になり、
66
+ 乖離の有無は判定できていない——「乖離なし」ではない)。
67
+
68
+ `reconfigure`直後は、identityの確認requestが400msで打ち切られるため一時的に`unattested`を返すことが
69
+ ある(daemonの起動直後と競合する)。数秒おいて引き直せば`running`になる。続くようなら本物の不整合で、
70
+ `reachable`がtrueでも公開面は認証できていない。
71
+
72
+ | field | 意味 |
73
+ | --- | --- |
74
+ | `pid` | 応答しているprocessのpid |
75
+ | `version` | そのprocessが読み込んでいるLatticeの版 |
76
+ | `node_path`・`node_version` | そのprocessを実行しているNodeの実体pathと版 |
77
+ | `bridge_path` | そのprocessが実行しているbridge script |
78
+
79
+ **`runtime_drift`** — 両者の食い違い。空配列は「差が無い」または「`runtime`が名乗っていないので
80
+ 判定できない」のどちらかである。
81
+
82
+ | 値 | 意味 |
83
+ | --- | --- |
84
+ | `bridge_path` | 常駐設定と違うtreeのcodeが走っている(開発treeの残骸など) |
85
+ | `node_path` | 常駐設定が指すnodeと実走nodeが別実体。焼くのは意図的にaliasなので、比較はrealpathで行う |
86
+ | `version` | npm更新後まだ旧moduleを保持している |
87
+
88
+ **`remedy`** — 自己解消しない状態にだけ、打つべきコマンドが入る。出るのは次の4つ。
89
+
90
+ - `persistence.node_exists`または`bridge_exists`がfalse(起動対象が消えた)
91
+ - `persistence.state`が`not_installed`(bridgeは有効なのに常駐設定が無い。いま走っているdaemonが
92
+ 最後の1つで、再起動しても戻らない)
93
+ - `persistence.state`が`unreadable`(常駐設定を読めない)
94
+ - `runtime_drift`に`node_path`または`bridge_path`がある
95
+
96
+ `version`だけの差には`remedy`を出さない。daemonは60秒ごとにon-diskのpackage.jsonと自分の版を
97
+ 突き合わせ、差があれば自ら終了してsupervisorに新codeで起動し直させる。放っておいて最大1分ほどで
98
+ 解消するので、コマンドを出す状態ではない。自己解消する差にコマンドを出すと、本物の障害が埋もれる。
99
+
100
+ `remedy`が出たら`reconfigure`で作り直す。plistやlauncherを手で書き換えない。
101
+
102
+ ```bash
103
+ lattice bridge reconfigure --json
104
+ ```
105
+
106
+ macOSのLaunchAgent載せ直しでは、`launchctl bootout`のexit 0はunload受付だけである。labelが
107
+ domainから消える(`launchctl print`が113)前に`bootstrap`すると、launchdは
108
+ `5 Input/output error`を返す。`reconfigure`はprint 113を待ってから載せる(0.60.7・
109
+ [ADR 0179](adr/0179-launchctl-bootout-completes-when-print-returns-113.md))。失敗時は
110
+ launchctlのexit codeとstderrが`lattice.cli_error.v2`の`detail`に残る。
111
+
112
+ ### 公開面から自分のprojectが消えた時(`last_heartbeat`)
113
+
114
+ hubへ繋いでいる端末では、`runtime.last_heartbeat`が最後にhubへ名乗った結果を持つ。公開一覧で
115
+ 自分のprojectがofflineになっている時、原因が端末側か配線側かはここで分かれる。
116
+
117
+ | `state` | 意味 | 打つ手 |
118
+ | --- | --- | --- |
119
+ | `accepted` | 全件受理された。公開面に出ていないならhub側の可視性設定を見る | — |
120
+ | `partial` | 一部が他の生きた端末に所有されている。`rejected_projects`が名指しする | 意図した端末なら放置。奪うなら`adopt` |
121
+ | `rejected` | hubがrequest全体を拒否した。`detail`にtyped code | detailのcodeで分岐 |
122
+ | `unreachable` | hubへ届かない。配線かhubの停止 | hubのURLと生死を確認 |
123
+ | `skipped_no_projects` | 配信しているprojectが0件。名乗るものが無い | 正常な静止。公開したいならそのrepoで作業する |
124
+ | `skipped_no_dashboard` | dashboard daemonを観測できない。配信そのものが立っていない | daemonの生死を見る。`todo`系commandを1回打てば起動する |
125
+ | `null` | hub未設定、またはまだ1回も送っていない | — |
126
+
127
+ 名乗る集合はdaemonが実際に配信している集合そのもの(ADR 0165)。配信集合は
128
+ `last_seen_at`が1週間以内、またはactive run、または監査待ちPhaseがあるprojectである。
129
+ 人がCLIを叩かなくても、その条件を満たす限り公開面に残る。1週間を超え、runも監査待ちも
130
+ 無いprojectは配信から外れる。heartbeatの90秒TTLは、配信そのものが止まった時
131
+ (daemon停止)にofflineへ落とすためのもので、鮮度窓ではない。
132
+
133
+ ### 焼き込むnode pathの選び方(ADR 0163)
134
+
135
+ 常駐設定へ焼くnodeのpathは、版付きの実体(homebrewの`Cellar/node/<version>/bin/node`、nvm-windowsの
136
+ 版ディレクトリ)ではなく、**同じbinaryを指すとrealpathで検証できた安定alias**(`/opt/homebrew/bin/node`、
137
+ `C:\Program Files\nodejs\node.exe`)を選ぶ。`brew upgrade node`が旧versionのディレクトリごと消しても
138
+ 起動対象が残るようにするためである。
139
+
140
+ 安定aliasを検証できない環境(shim方式のasdf/volta等。shimは自身のlauncherへ解決されるので実体と
141
+ 一致しない)では、版付きpathのまま焼く。検証していないpathを推測で焼けば別のnodeでdaemonが起動して
142
+ しまうためで、そこでの防御は起動継続ではなく`node_exists`による消滅の可視化である。
143
+
144
+ > **0.55.0より前に設定した常駐は自動では移行しない。** 焼き直しは`reconfigure`を実行した時にだけ
145
+ > 起きるので、Latticeを更新しただけの端末は版付きpathを抱えたままになる。更新後に各端末で
146
+ > `lattice bridge reconfigure --json`を1回打つ。現在どちらを焼いているかは`persistence.node_path`で読める。
147
+
148
+ なお`setup`/`reconfigure`をnode_modules配下でない実体(開発tree)から実行すると、結果の`warnings`へ
149
+ `BRIDGE_PERSISTED_FROM_DEVELOPMENT_TREE`が入る(該当しなければ空配列)。そのtreeを動かすと常駐が
150
+ 止まり、`npm`更新も反映されない。開発treeから常駐させること自体は正当な操作なので拒否はしない。
151
+
152
+ ## listen IPがDHCPで動く場合
153
+
154
+ 設定したlisten IPがホストから消えると、古いsocketは死んだアドレスへ取り残され、LANから到達できなくなる。
155
+ daemonは各reconcileで実効アドレスを解決し直し、同一subnet(IPv4 /24、IPv6 /64)に生きたアドレスがあれば
156
+ そこへbindし直す。VPNや別NICなど異なるnetworkのアドレスは採用せず、候補が無ければ
157
+ `BRIDGE_LISTEN_ADDRESS_ABSENT`で公開socketをfail-closedにする。再bind先は許可Hostへ自動で加わる。
158
+
159
+ `LATTICE_BRIDGE_REGISTRAR_SSH_HOST`と`LATTICE_BRIDGE_REGISTRAR_SCRIPT`を両方設定すると、新しいbindingを
160
+ 張るたびに`ssh <host> <script> <port>`でreverse proxy hostへ自己登録する。アドレスは送らず、remote側が
161
+ ssh送信元から決めるため、各hostは自分自身しか登録できない。登録の失敗はbridgeを落とさずstderrへ
162
+ typedに報告する。この配線が無いと、Caddy等が持つリテラルはlease変更のたびに黙って陳腐化する。
163
+
164
+ ## reverse proxyへ逆トンネルで繋ぐ(LAN bindを使わない)
165
+
166
+ reverse proxy hostへsshで到達できるなら、LANへbindせずloopbackだけで公開できる。bridgeが動くhostから
167
+ 接続しに行くため、そのhostのLAN addressはreverse proxyのどこにも現れず、追従も自己登録も不要になる。
168
+
169
+ ```bash
170
+ lattice bridge setup --listen 127.0.0.1 --port 53939 --dashboard --allow-host lattice.example.com --json
171
+ ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \
172
+ -R 172.18.0.1:53939:127.0.0.1:53939 proxy-host
173
+ ```
174
+
175
+ reverse proxyはこの固定endpointだけを見る。転送口のbind先は、reverse proxyが到達できるaddressにする。
176
+ Docker上のreverse proxyでは、containerの`127.0.0.1`はcontainer自身のloopbackでありhostのそれではないため、
177
+ hostのloopbackへ開いた口には届かない。対象networkのgateway(`docker network inspect`の`Gateway`)へbindする。
178
+
179
+ sshdは既定の`GatewayPorts no`だと`127.0.0.1`にしかbindできない。`clientspecified`にすると、clientが明示した
180
+ addressだけにbindする(`yes`と違い全interfaceへは晒さない)。host firewallがINPUTをDROPしている場合は、
181
+ その1 portだけを許可する。`ExitOnForwardFailure=yes`は、転送口を開けないまま接続だけ生かす状態を防ぐ。
182
+ 常駐はprocess supervisorのKeepAliveに任せ、切断時は張り直す。
183
+
184
+ ## Docker Caddy/Cloudflare Tunnelへ接続する
185
+
186
+ bridgeを有効化したMacとreverse proxy hostの間で、まず許可Hostを付けたLAN到達を確認する。
187
+ この段階が失敗している時はDNSやTunnelを追加しない。
188
+
189
+ ```bash
190
+ curl --fail --header 'Host: lattice.example.com' \
191
+ http://MAC_LAN_IP:BRIDGE_PORT/projects/
192
+ ```
193
+
194
+ Caddyは既存のDocker networkと証明書運用を維持し、Lattice用siteだけを追加する。
195
+
196
+ ```caddyfile
197
+ lattice.example.com {
198
+ reverse_proxy MAC_LAN_IP:BRIDGE_PORT {
199
+ flush_interval -1
200
+ }
201
+ }
202
+ ```
203
+
204
+ 本番反映はcontainer内で`caddy validate`を通してから`caddy reload`する。Caddyfileを単一ファイルで
205
+ bind mountしている構成では、atomic renameでhost側fileを置換するとcontainerが旧inodeを参照し続ける。
206
+ 更新前backupを残し、inodeを維持するin-place更新を使うか、directory bind mountへ変更する。
207
+
208
+ remote-managed Cloudflare Tunnelでは、Tunnel実行tokenを設定APIの代用にしない。Cloudflareの正規管理面で
209
+ public hostname `lattice.kitepon.dev` を次のoriginへ対応付ける。
210
+
211
+ - Service: `https://caddy:443`
212
+ - TLS Origin Server Name: `lattice.kitepon.dev`(管理面に同等の設定がある場合は`Match SNI to Host`でもよい)
213
+
214
+ `http://caddy:80`は選ばない。CaddyのHTTPからHTTPSへのredirectをTunnelがorigin応答として返す構成は、
215
+ 外部requestが同じ公開URLへ戻るredirect loopになり得るためである。外部gateではredirectを追って200にせず、
216
+ 最初の応答がHTTPSの200であることを確認する。
217
+
218
+ 受入は次の3 gateを独立して記録し、後段の成功で前段を代用しない。
219
+
220
+ 1. **LAN bridge**: reverse proxy hostから許可Host付きで`http://MAC_LAN_IP:BRIDGE_PORT/projects/`が200。
221
+ 2. **Docker Caddy**: Caddyへ`Host: lattice.kitepon.dev`を付けたHTTPS requestが200。証明書検証を省略する
222
+ 内部probeを外部公開成功の証拠にはしない。
223
+ 3. **Cloudflare public HTTPS**: `https://lattice.kitepon.dev/projects/`がredirectなしで200となり、一覧から開いた
224
+ `/projects/<project_id>/`のHTML titleが`Lattice — <project名> 依存工程図`である。
225
+
226
+ 公開viewerの404も、ブラウザとAPIの両契約を別々に確認する。未知URLへ`Accept: text/html`を
227
+ 付けたrequestはHTTP 404かつ`Content-Type: text/html`で、`noindex, nofollow`と
228
+ `/projects/`、`https://kitepon.dev/`への戻り先を持つ。`Accept: application/json`では
229
+ HTTP 404かつ`Content-Type: application/json`で、既存の
230
+ `lattice.todo_gantt_http_error.v1`を返す。
231
+
232
+ ```bash
233
+ curl --silent --show-error --include --header 'Accept: text/html' \
234
+ https://lattice.kitepon.dev/unknown
235
+ curl --silent --show-error --include --header 'Accept: application/json' \
236
+ https://lattice.kitepon.dev/unknown
237
+ ```
238
+
239
+ 外部gateはHTMLだけで閉じず、各projectの
240
+ `https://lattice.kitepon.dev/projects/<project_id>/events`も確認する。応答は200かつ
241
+ `Content-Type: text/event-stream`で、接続直後に`event: state`と現在の`head_digest`を返さなければならない。
242
+ 接続を開いたまま正規のTodo更新を行い、新しい`state`が同じstreamへ届くことを確認する。切断後に再接続しても
243
+ 再び初回`state`が届き、そのdigestが最新headと一致することまでを継続・再接続gateとする。
244
+
245
+ ```bash
246
+ curl --fail --show-error --include --no-buffer --max-time 15 \
247
+ https://lattice.kitepon.dev/projects/PROJECT_ID/events
248
+ ```
@@ -1,57 +1,57 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.executor_packet.v1.schema.json",
4
- "title": "lattice.executor_packet.v1",
5
- "description": "The single context packet issued at dispatch. It is the machine record of the context an executor received.",
6
- "$comment": "Runtime validation additionally enforces canonical JSON bytes, the self-digest rule for `packet_digest`, and that `context_content_digest` equals the SHA-256 of the canonical JSON projection of exactly {todo_id, task_ref, scope, base_sha, verifier_refs, forbidden_operations} — plan attribution fields are excluded so that an epoch rebind is provably content-preserving.",
7
- "type": "object",
8
- "additionalProperties": false,
9
- "required": [
10
- "schema",
11
- "packet_id",
12
- "todo_id",
13
- "task_ref",
14
- "scope",
15
- "base_sha",
16
- "plan_ref",
17
- "plan_epoch",
18
- "verifier_refs",
19
- "forbidden_operations",
20
- "context_content_digest",
21
- "packet_digest"
22
- ],
23
- "properties": {
24
- "schema": { "const": "lattice.executor_packet.v1" },
25
- "packet_id": { "$ref": "#/$defs/identifier" },
26
- "todo_id": {
27
- "$ref": "#/$defs/identifier",
28
- "$comment": "Carried through from `run_request.v1`. It is not qualified by any TODO store project/plan/revision identity."
29
- },
30
- "task_ref": { "$ref": "#/$defs/identifier" },
31
- "scope": { "type": "object" },
32
- "base_sha": { "$ref": "#/$defs/gitSha" },
33
- "plan_ref": { "$ref": "#/$defs/identifier" },
34
- "plan_epoch": { "type": "integer", "minimum": 0 },
35
- "verifier_refs": {
36
- "type": "array",
37
- "maxItems": 256,
38
- "items": { "type": "string" }
39
- },
40
- "forbidden_operations": {
41
- "type": "array",
42
- "maxItems": 256,
43
- "items": { "type": "string" },
44
- "description": "Compatibility field. Lattice-produced packets use an empty array; operation authority belongs to the host."
45
- },
46
- "context_content_digest": { "$ref": "#/$defs/digest" },
47
- "packet_digest": { "$ref": "#/$defs/digest" }
48
- },
49
- "$defs": {
50
- "identifier": {
51
- "type": "string",
52
- "pattern": "^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$"
53
- },
54
- "digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
55
- "gitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" }
56
- }
57
- }
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/kitepon/Lattice/blob/main/docs/schemas/lattice.executor_packet.v1.schema.json",
4
+ "title": "lattice.executor_packet.v1",
5
+ "description": "The single context packet issued at dispatch. It is the machine record of the context an executor received.",
6
+ "$comment": "Runtime validation additionally enforces canonical JSON bytes, the self-digest rule for `packet_digest`, and that `context_content_digest` equals the SHA-256 of the canonical JSON projection of exactly {todo_id, task_ref, scope, base_sha, verifier_refs, forbidden_operations} — plan attribution fields are excluded so that an epoch rebind is provably content-preserving.",
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [
10
+ "schema",
11
+ "packet_id",
12
+ "todo_id",
13
+ "task_ref",
14
+ "scope",
15
+ "base_sha",
16
+ "plan_ref",
17
+ "plan_epoch",
18
+ "verifier_refs",
19
+ "forbidden_operations",
20
+ "context_content_digest",
21
+ "packet_digest"
22
+ ],
23
+ "properties": {
24
+ "schema": { "const": "lattice.executor_packet.v1" },
25
+ "packet_id": { "$ref": "#/$defs/identifier" },
26
+ "todo_id": {
27
+ "$ref": "#/$defs/identifier",
28
+ "$comment": "Carried through from `run_request.v1`. It is not qualified by any TODO store project/plan/revision identity."
29
+ },
30
+ "task_ref": { "$ref": "#/$defs/identifier" },
31
+ "scope": { "type": "object" },
32
+ "base_sha": { "$ref": "#/$defs/gitSha" },
33
+ "plan_ref": { "$ref": "#/$defs/identifier" },
34
+ "plan_epoch": { "type": "integer", "minimum": 0 },
35
+ "verifier_refs": {
36
+ "type": "array",
37
+ "maxItems": 256,
38
+ "items": { "type": "string" }
39
+ },
40
+ "forbidden_operations": {
41
+ "type": "array",
42
+ "maxItems": 256,
43
+ "items": { "type": "string" },
44
+ "description": "Compatibility field. Lattice-produced packets use an empty array; operation authority belongs to the host."
45
+ },
46
+ "context_content_digest": { "$ref": "#/$defs/digest" },
47
+ "packet_digest": { "$ref": "#/$defs/digest" }
48
+ },
49
+ "$defs": {
50
+ "identifier": {
51
+ "type": "string",
52
+ "pattern": "^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$"
53
+ },
54
+ "digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
55
+ "gitSha": { "type": "string", "pattern": "^[0-9a-f]{40}$" }
56
+ }
57
+ }