peertable 0.3.2 → 0.3.4

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.
@@ -51,6 +51,39 @@ else
51
51
  mode=lattice
52
52
  fi
53
53
 
54
+ # Lattice 併用モードは、登録に使う公開CLIと同梱work-order binaryを、projectへ
55
+ # 何か置く前に確定する。通常はglobal installされた lattice の隣を使う。
56
+ # release前のsource treeを実測する時だけ、2つのenvで同じtreeのbinを明示できる。
57
+ lattice_cli=""
58
+ work_order_binary=""
59
+ node_binary=""
60
+ if [ "$mode" = "lattice" ]; then
61
+ lattice_cli="${LATTICE_CLI:-$(command -v lattice 2>/dev/null || true)}"
62
+ [ -n "$lattice_cli" ] || { echo "ERROR: lattice CLI が見つからない" >&2; exit 1; }
63
+ [ -x "$lattice_cli" ] || { echo "ERROR: lattice CLI が実行可能fileでない: $lattice_cli" >&2; exit 1; }
64
+ lattice_cli=$(node -e 'process.stdout.write(require("node:fs").realpathSync(process.argv[1]))' "$lattice_cli")
65
+
66
+ work_order_binary="${LATTICE_WORK_ORDER_ADAPTER_BINARY:-$(dirname "$lattice_cli")/lattice-work-order-adapter.mjs}"
67
+ [ -f "$work_order_binary" ] && [ -x "$work_order_binary" ] || {
68
+ echo "ERROR: Lattice work-order adapter binary が見つからないか実行不能: $work_order_binary" >&2
69
+ exit 1
70
+ }
71
+ work_order_binary=$(node -e 'process.stdout.write(require("node:fs").realpathSync(process.argv[1]))' "$work_order_binary")
72
+ node_binary=$(node -e 'process.stdout.write(require("node:fs").realpathSync(process.execPath))')
73
+ [ -x "$node_binary" ] || { echo "ERROR: Node executable が実行可能fileでない: $node_binary" >&2; exit 1; }
74
+
75
+ # config_refはgit root相対の公開契約。subdirectoryをprojectとして受けると別の
76
+ # `.lattice/` を作ってしまうので、黙って親repoへ登録せずtypedに止める。
77
+ project_root=$(node -e 'process.stdout.write(require("node:fs").realpathSync(process.argv[1]))' "$proj")
78
+ git_root=$(git -C "$proj" rev-parse --show-toplevel 2>/dev/null || true)
79
+ [ -n "$git_root" ] || { echo "ERROR: Lattice 併用モードのprojectはgit repositoryでなければならない: $proj" >&2; exit 1; }
80
+ git_root=$(node -e 'process.stdout.write(require("node:fs").realpathSync(process.argv[1]))' "$git_root")
81
+ [ "$project_root" = "$git_root" ] || {
82
+ echo "ERROR: project_dirはgit rootを指さなければならない: project=$project_root git_root=$git_root" >&2
83
+ exit 1
84
+ }
85
+ fi
86
+
54
87
  mkdir -p "$tdir/roles"
55
88
  cp "$tpl/charter.md" "$tdir/CLAUDE.md"
56
89
  if [ "$mode" = "standalone" ]; then
@@ -92,6 +125,67 @@ fi
92
125
 
93
126
  lattice_preexisting=false
94
127
  [ -d "$proj/.lattice" ] && lattice_preexisting=true
128
+ runtime_preexisting=false
129
+ [ -d "$proj/.lattice/runtime" ] && runtime_preexisting=true
130
+
131
+ # adapter registry/config/spool はhost固有のruntime stateであり、sourceとして追跡しない。
132
+ # `.lattice/`の一部を正本として追跡するprojectでもruntimeだけをroot相対で除外する。
133
+ added_runtime_exclude=false
134
+ if [ "$mode" = "lattice" ] && [ -d "$proj/.git" ] \
135
+ && ! grep -qx '/\.lattice/runtime/' "$proj/.git/info/exclude" 2>/dev/null; then
136
+ mkdir -p "$proj/.git/info"
137
+ echo '/.lattice/runtime/' >> "$proj/.git/info/exclude"
138
+ added_runtime_exclude=true
139
+ fi
140
+
141
+ # managed run の仕事口をLattice runtime stateとして用意する。configを`.team/`
142
+ # に置くとarchive teardownでregistryだけが残って壊れるため、registryと同じ
143
+ # `.lattice/runtime/`の寿命へ揃える。席はこのspoolへ直接触れない。
144
+ work_order_adapter=false
145
+ work_order_spool_ref=""
146
+ if [ "$mode" = "lattice" ]; then
147
+ work_order_root="$proj/.lattice/runtime/work-order-adapter"
148
+ work_order_spool="$work_order_root/spool"
149
+ work_order_config="$work_order_root/config.json"
150
+ work_order_registration="$tdir/work-order-adapter-registration.json"
151
+ work_order_config_ref=".lattice/runtime/work-order-adapter/config.json"
152
+ work_order_spool_ref=".lattice/runtime/work-order-adapter/spool"
153
+
154
+ mkdir -p "$work_order_spool/orders" "$work_order_spool/reports"
155
+ chmod 700 "$work_order_root" "$work_order_spool" "$work_order_spool/orders" "$work_order_spool/reports"
156
+ work_order_spool=$(node -e 'process.stdout.write(require("node:fs").realpathSync(process.argv[1]))' "$work_order_spool")
157
+
158
+ node -e '
159
+ const { writeFileSync } = require("node:fs");
160
+ const [target, spool] = process.argv.slice(1);
161
+ writeFileSync(target, `${JSON.stringify({
162
+ schema: "lattice.work_order_adapter_config.v1",
163
+ spool_dir: spool,
164
+ })}\n`, { mode: 0o600 });
165
+ ' "$work_order_config" "$work_order_spool"
166
+ chmod 600 "$work_order_config"
167
+ node -e '
168
+ const { writeFileSync } = require("node:fs");
169
+ const [target, binary, script, configRef] = process.argv.slice(1);
170
+ writeFileSync(target, `${JSON.stringify({
171
+ schema: "lattice.runtime_adapter_registration_input.v2",
172
+ adapter_kind: "work-order",
173
+ launch_kind: "host_binary",
174
+ binary_path: binary,
175
+ argv: [script],
176
+ config_ref: configRef,
177
+ host_driven_epoch: true,
178
+ })}\n`, { mode: 0o600 });
179
+ ' "$work_order_registration" "$node_binary" "$work_order_binary" "$work_order_config_ref"
180
+ chmod 600 "$work_order_registration"
181
+
182
+ (
183
+ cd "$proj"
184
+ "$lattice_cli" run adapter register --input "$work_order_registration"
185
+ )
186
+ work_order_adapter=true
187
+ echo "work-order adapter: binary=$node_binary argv=$work_order_binary config=$work_order_config_ref spool=$work_order_spool_ref" >&2
188
+ fi
95
189
 
96
190
  # Lattice 併用モードだけ、工程表の右ペインへ円卓を差す(決定53・明示的コネクタ)。
97
191
  # 公開URL基底は `PEERTABLE_PUBLIC_URL`(クオ環境: https://peertable.kitepon.dev)。
@@ -113,6 +207,6 @@ if [ ${#phases[@]} -gt 0 ]; then
113
207
  phases_json="[${phases_json%,}]"
114
208
  fi
115
209
 
116
- printf '{"room":"%s","server_url":"%s","public_url":"%s","mode":"%s","plan_key":"%s","phases":%s,"added_exclude":%s,"lattice_preexisting":%s,"added_root_mcp":%s,"added_mcp_exclude":%s,"external_pane":%s,"project_json_preexisting":%s}\n' \
117
- "$room" "$url" "$public_url" "$mode" "$plan" "$phases_json" "$added_exclude" "$lattice_preexisting" "$added_root_mcp" "$added_mcp_exclude" "$external_pane" "$project_json_preexisting" > "$tdir/setup-state.json"
210
+ printf '{"room":"%s","server_url":"%s","public_url":"%s","mode":"%s","plan_key":"%s","phases":%s,"added_exclude":%s,"lattice_preexisting":%s,"runtime_preexisting":%s,"added_runtime_exclude":%s,"added_root_mcp":%s,"added_mcp_exclude":%s,"external_pane":%s,"project_json_preexisting":%s,"work_order_adapter":%s,"work_order_spool_ref":"%s"}\n' \
211
+ "$room" "$url" "$public_url" "$mode" "$plan" "$phases_json" "$added_exclude" "$lattice_preexisting" "$runtime_preexisting" "$added_runtime_exclude" "$added_root_mcp" "$added_mcp_exclude" "$external_pane" "$project_json_preexisting" "$work_order_adapter" "$work_order_spool_ref" > "$tdir/setup-state.json"
118
212
  echo "scaffold done: $tdir"
@@ -1,17 +1,34 @@
1
1
  #!/bin/bash
2
- # Peertable teardown の機械部分。実行前にメンバーセッションを終了しておくこと(AI pty_close で行う)。
3
- # usage: teardown.sh <project_dir>
2
+ # Peertable teardown の機械部分。席(tmux)も本 script が畳む。
3
+ # usage: teardown.sh <project_dir> [--purge]
4
+ # 既定(archive): room のログを `docs/archive/` へ書き出し、`.lattice/` は残して畳む
5
+ # --purge : 何も残さず全部消す(従来の痕跡ゼロ・ゲスト project 向け)
4
6
  # 書込トークンは環境変数 PEERTABLE_POST_TOKEN から取る(`~/.config/peertable.env` は export 付きで定義すること)。
5
7
  #
6
8
  # 撤去は「何が実施され、何が実施されなかったか」を1行ずつ出す。room 削除だけがトークンを要するので、
7
9
  # そこが失敗しても残りの撤去は続行し、未実施を明示して非ゼロで終わる(黙って中断しない・決定58)。
10
+ #
11
+ # **既定が archive なのはオーナー裁定(2026-08-09・決定61)**。痕跡ゼロを既定にしていた時代は、
12
+ # 畳んだ瞬間に room の会話ログ(server 側の正本)と Lattice store が消えていた——**卓の議論と
13
+ # 工程の記録は、卓そのものより寿命が長い**。ゲスト project を汚さない不可侵原則は `--purge` が持つ。
8
14
  set -e
9
15
  proj="$1"
16
+ mode=archive
17
+ for arg in "${@:2}"; do
18
+ case "$arg" in
19
+ --purge) mode=purge ;;
20
+ --archive) mode=archive ;;
21
+ *) echo "ERROR: 未知の引数: ${arg}(受けるのは --purge / --archive だけ)" >&2; exit 1 ;;
22
+ esac
23
+ done
10
24
  state="$proj/.team/setup-state.json"
11
25
  room=$(python3 -c "import json;print(json.load(open('$state'))['room'])")
12
26
  url=$(python3 -c "import json;print(json.load(open('$state'))['server_url'])")
13
27
  added=$(python3 -c "import json;print(json.load(open('$state'))['added_exclude'])")
14
28
  lat_pre=$(python3 -c "import json;print(json.load(open('$state'))['lattice_preexisting'])")
29
+ runtime_pre=$(python3 -c "import json;d=json.load(open('$state'));print(d.get('runtime_preexisting', True))")
30
+ added_runtime_ex=$(python3 -c "import json;d=json.load(open('$state'));print(d.get('added_runtime_exclude', False))")
31
+ work_order_adapter=$(python3 -c "import json;d=json.load(open('$state'));print(d.get('work_order_adapter', False))")
15
32
  # 旧 state(added_root_mcp 不在・手動フォールバック時代の root_mcp_json_fallback)も読む
16
33
  added_mcp=$(python3 -c "import json;d=json.load(open('$state'));print(d.get('added_root_mcp', d.get('root_mcp_json_fallback', False)))")
17
34
  added_mcp_ex=$(python3 -c "import json;d=json.load(open('$state'));print(d.get('added_mcp_exclude', d.get('root_mcp_json_fallback', False)))")
@@ -22,6 +39,42 @@ skip() { echo "teardown: [スキップ] $*"; }
22
39
  miss() { echo "teardown: [未実施] $*" >&2; fail=1; }
23
40
  yes_() { [ "$1" = "True" ] || [ "$1" = "true" ]; }
24
41
 
42
+ echo "teardown: mode=${mode}(archive=ログとstoreを残す/purge=痕跡ゼロ)"
43
+
44
+ # ---- ログの控え(archive だけ)。room 自体は残るので、これは repo 側の写し ----
45
+ # 失敗しても撤去は続ける(room に原本があるため)。--purge の時だけ「消す前の最後の機会」になる
46
+ log_saved=skip
47
+ if [ "$mode" = archive ]; then
48
+ arc="$proj/docs/archive"
49
+ out="$arc/room-log_${room}_$(date +%Y%m%d-%H%M%S).md"
50
+ mkdir -p "$arc"
51
+ if python3 "$(dirname "$0")/archive-room-log.py" "$url" "$room" "$out"; then
52
+ did "room ログの写しを ${out#"$proj/"} へ(原本は room に残る)"
53
+ log_saved=yes
54
+ else
55
+ miss "room ログの写しに失敗($url/api/$room が読めない)— **原本は room に残っている**ので撤去は続行する"
56
+ log_saved=no
57
+ fi
58
+ fi
59
+
60
+ # ---- 席(tmux)の終了。**`.team/` を消す前**に、この room の member だけを畳む ----
61
+ # `peer-*` を全部畳むと、同じマシンの別の卓を巻き込む(bridge が members 起点にしているのと同じ理由)
62
+ sock="${PEERTABLE_TMUX_SOCKET:-${TMPDIR}claude-tmux-sockets/claude.sock}"
63
+ seats=$(python3 "$(dirname "$0")/archive-room-log.py" --members "$url" "$room" 2>/dev/null || true)
64
+ if [ -z "$seats" ]; then
65
+ miss "席の終了 — member 一覧が取れず、畳む相手を特定できない。手で確認: tmux -S \"$sock\" list-sessions | grep peer-"
66
+ else
67
+ closed=0
68
+ for name in $seats; do
69
+ if tmux -S "$sock" has-session -t "peer-$name" 2>/dev/null; then
70
+ tmux -S "$sock" kill-session -t "peer-$name" && closed=$((closed + 1))
71
+ fi
72
+ done
73
+ if [ "$closed" -gt 0 ]; then did "席の終了(${closed}席)"; else skip "席の終了(生きている席なし)"; fi
74
+ left=$(tmux -S "$sock" list-sessions 2>/dev/null | grep -c '^peer-' || true)
75
+ [ "${left:-0}" -eq 0 ] || echo "teardown: [注記] 他の卓の peer-* が ${left}件 残っている(この卓のものではないので畳まない)"
76
+ fi
77
+
25
78
  # Codex 席の起床ブリッジ(決定54)。常駐 process なので、`.team/` を消す前に確実に止める
26
79
  if [ -f "$proj/.team/wakeup-bridge.json" ]; then
27
80
  # 停止に失敗しても **ここで止まらない**。`set -e` で落ちると、t6 の契約(各段の実施・未実施を
@@ -50,12 +103,66 @@ else
50
103
  skip "seat-status-bridge(起動記録なし)"
51
104
  fi
52
105
 
106
+ # 配車ブリッジ(managed run に載せた卓だけ立っている)。同じ理由で `.team/` を消す前に止める。
107
+ # 止め残すと、spool を見張り続ける常駐が次の run の order を拾って**卓が無いのに配車を投稿する**
108
+ if [ -f "$proj/.team/run-bridge.json" ]; then
109
+ if node "$(dirname "$0")/run-bridge.mjs" "$proj" --stop; then
110
+ did "run-bridge 停止"
111
+ else
112
+ miss "run-bridge 停止に失敗(常駐が残る)— 上の _STOP_FAILED を見て手で止める"
113
+ fi
114
+ else
115
+ skip "run-bridge(起動記録なし)"
116
+ fi
117
+
118
+ # setupが新しく作ったhost固有runtimeだけを撤去する。既存runtimeは他adapterや進行中runの
119
+ # 所有物を含み得るので触らない。runtimeを先に消してからexcludeを戻し、teardown後に
120
+ # untracked stateが露出する順序逆転を防ぐ。
121
+ if yes_ "$work_order_adapter" && ! yes_ "$runtime_pre"; then
122
+ rm -rf "$proj/.lattice/runtime"
123
+ did ".lattice/runtime/ 撤去(setup が新規作成したhost固有state)"
124
+ elif yes_ "$work_order_adapter"; then
125
+ skip ".lattice/runtime/(setup 以前から存在)"
126
+ else
127
+ skip ".lattice/runtime/(work-order adapter登録なし)"
128
+ fi
129
+
53
130
  # 外部ペイン(決定53)。`.team/` を消す前に戻す——退避先が `.team/` の中にある
54
131
  ext=$(python3 -c "import json;print(json.load(open('$state')).get('external_pane', False))")
55
132
  pj_pre=$(python3 -c "import json;print(json.load(open('$state')).get('project_json_preexisting', False))")
56
133
 
57
- # room 削除。トークンを要する唯一の段で、ここだけが外部サービスへの依存境界
58
- if [ -z "${PEERTABLE_POST_TOKEN:-}" ]; then
134
+ # ---- 解散(archive): **部屋は残し、メンバー登録だけ外す** ----
135
+ # 円卓の解散は「部屋を畳む」ではなく「集まりが散る」。部屋は場所であって、次の campaign
136
+ # 同じ部屋で続く——**過去ログが同じ部屋の履歴として繋がり、部屋は常に一つに見える**
137
+ # (オーナー裁定 2026-08-09・決定61)。参加者一覧だけ空にして、席が戻れば再登録される
138
+ if [ "$mode" = archive ]; then
139
+ if [ -z "${PEERTABLE_POST_TOKEN:-}" ]; then
140
+ miss "メンバー登録の解除 — TOKEN_MISSING: \`~/.config/peertable.env\` の定義が \`export\` 付きでないと子 process へ渡らない"
141
+ echo "teardown: [手当] 参加者は次で外せる: curl -X DELETE \"$url/api/$room/members/<名前>\" -H \"X-Peertable-Token: \$PEERTABLE_POST_TOKEN\"" >&2
142
+ elif [ -z "$seats" ]; then
143
+ skip "メンバー登録の解除(一覧が取れていない)"
144
+ else
145
+ # 履歴に解散の区切りを残す。**部屋が続く以上、どこで卓が変わったかが読めないと
146
+ # 過去ログが一続きの会話に見えてしまう**(次の campaign の発言と地続きになる)
147
+ body="解散。この卓はここまで。参加者: ${seats}。部屋と過去ログはこのまま残り、次の卓も同じ部屋で続く。"
148
+ python3 -c "
149
+ import json,sys,urllib.request
150
+ req=urllib.request.Request('$url/api/$room/messages', method='POST',
151
+ data=json.dumps({'from':'system','to':'all','body':'''$body'''}).encode(),
152
+ headers={'Content-Type':'application/json','X-Peertable-Token':'$PEERTABLE_POST_TOKEN'})
153
+ urllib.request.urlopen(req, timeout=10).read()
154
+ " 2>/dev/null && did "解散の区切りを履歴へ" || skip "解散の区切り(投稿できず・撤去は続行)"
155
+ n=0
156
+ for name in $seats; do
157
+ c=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "$url/api/$room/members/$name" -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" || true)
158
+ [ "$c" = 200 ] && n=$((n + 1))
159
+ done
160
+ did "メンバー登録の解除(${n}名)— **部屋と過去ログは残す**($url/$room)"
161
+ fi
162
+ # room 削除は --purge だけ。トークンを要する唯一の段で、ここだけが外部サービスへの依存境界
163
+ elif [ "$log_saved" = no ]; then
164
+ miss "room 削除 $room — ログを保全できていないので消さない(保全より先に消すと会話は二度と戻らない)"
165
+ elif [ -z "${PEERTABLE_POST_TOKEN:-}" ]; then
59
166
  miss "room 削除 $room — TOKEN_MISSING: PEERTABLE_POST_TOKEN が空。\`~/.config/peertable.env\` の定義が \`export\` 付きでないと子 process へ渡らない"
60
167
  else
61
168
  code=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "$url/api/$room" -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" || true)
@@ -99,6 +206,14 @@ else
99
206
  skip "exclude の /.mcp.json(setup が足していない)"
100
207
  fi
101
208
 
209
+ if yes_ "$added_runtime_ex"; then
210
+ grep -vx '/\.lattice/runtime/' "$proj/.git/info/exclude" > "$proj/.git/info/exclude.tmp" || true
211
+ mv "$proj/.git/info/exclude.tmp" "$proj/.git/info/exclude"
212
+ did "exclude から /.lattice/runtime/ を撤去"
213
+ else
214
+ skip "exclude の /.lattice/runtime/(setup が足していない)"
215
+ fi
216
+
102
217
  if yes_ "$added"; then
103
218
  grep -vx '\.team/' "$proj/.git/info/exclude" > "$proj/.git/info/exclude.tmp" || true
104
219
  mv "$proj/.git/info/exclude.tmp" "$proj/.git/info/exclude"
@@ -107,11 +222,15 @@ else
107
222
  skip "exclude の .team/(setup が足していない)"
108
223
  fi
109
224
 
110
- if [ "$lat_pre" = "False" ] || [ "$lat_pre" = "false" ]; then
111
- rm -rf "$proj/.lattice"
112
- did ".lattice/ 削除(setup が作ったもの)"
113
- else
225
+ if [ "$lat_pre" = "True" ] || [ "$lat_pre" = "true" ]; then
114
226
  skip ".lattice/(setup 以前から存在)"
227
+ elif [ "$mode" = archive ]; then
228
+ # 工程正本を残すのが archive の本体。**残すだけでは git が知らない**ので、追跡へ入れるのは人の判断
229
+ did ".lattice/ を残す(工程正本・\`lattice todo status\` と \`gantt serve\` が読む)"
230
+ echo "teardown: [注記] .lattice/ は git 追跡外のままなら次の clone に残らない。残すなら commit すること"
231
+ else
232
+ rm -rf "$proj/.lattice"
233
+ did ".lattice/ 削除(setup が作ったもの・--purge)"
115
234
  fi
116
235
 
117
236
  if [ "$fail" -ne 0 ]; then
@@ -91,7 +91,9 @@ setInterval(async () => {
91
91
  function dispatch(msg) {
92
92
  for (const seat of seats) {
93
93
  if (msg.from === seat) continue
94
- if (msg.to !== 'all' && msg.to !== seat) continue
94
+ // 複数人宛は `to_names` が実宛先を持つ(server は旧 client のために `to` 'all' へ倒す)。
95
+ // ここで実宛先を見ないと、名指しされていない Codex 席まで起こしてしまう。
96
+ if (Array.isArray(msg.to_names) ? !msg.to_names.includes(seat) : (msg.to !== 'all' && msg.to !== seat)) continue
95
97
  pending.get(seat).push(msg)
96
98
  }
97
99
  }
@@ -1,13 +1,13 @@
1
1
  # チーム憲章
2
2
 
3
- これはチーム作業である。自分の claim したタスクの完了はミッションの完了ではない。全タスク完了までチームは解散しない。
3
+ これはチーム作業である。自分が引き受けたタスクの完了はミッションの完了ではない。全タスク完了までチームは解散しない。
4
4
 
5
5
  1. 拘束力を持つのは**工程正本への記録**と room での決定だけ(工程正本=Lattice 併用モードなら Lattice の todo 記録、単独円卓モードなら room の宣言そのもの)。DM(個別宛)で決まったことは決定ではない。部位を跨ぐ合意・設計判断は必ず room 全員宛で行う
6
- 2. 進捗は room に一行で報告する: claim 時・join 時・完了時・詰まり自覚時・方針変更時。task 外でも成果物になる作業(実測・検証・CI)は着手前に一言。仲間を五里霧中に置かない
7
- 3. claim の手順: room へ「[claim] <タスク>」を全員宛で投稿し、直後に read_unread で直前ログを確認する(タスクの呼び名は Lattice 併用モードなら task_id、単独円卓モードなら `.team/tasks.md` の議題名)。同じタスクへの先行 claim があれば取り下げるか「[join] <タスク>」へ切り替える
6
+ 2. 進捗は room に一行で報告する: managed run なら配車への受諾・辞退、その他の卓なら claimjoin、共通して完了時・詰まり自覚時・方針変更時。task 外でも成果物になる作業(実測・検証・CI)は着手前に一言。仲間を五里霧中に置かない
7
+ 3. 仕事を引き受ける手順は実行方式で分かれる。managed run では task を Lattice、候補席を bridge が選び、`[配車] <タスク> → <席>` はその提示を全員へ見せるだけで割当ではない。提示された席が `[受諾] <タスク>` を返した時だけ束縛が成立し、`[辞退] <タスク> <理由>` なら bridge が別席へ再配車する。この経路で `[claim]` を重ねない。managed run でない卓では、room へ `[claim] <タスク>` を全員宛で投稿し、直後に read_unread で直前ログを確認する(タスクの呼び名は Lattice 併用モードなら task_id、単独円卓モードなら `.team/tasks.md` の議題名)。同じタスクへの先行 claim があれば取り下げるか `[join] <タスク>` へ切り替える
8
8
  4. 分からないことは room で聞く。台帳はない。自分の変更が他の部位に影響するなら、聞かれる前に room 全員宛で通知する
9
9
  5. 判断は情報を持つ者がする。タスクは席ではなく現場。合流(join)は歓迎される。詰まった仲間には目を貸す。手が足りないだけなら自分のサブエージェントを使う(room 報告不要)。視点が足りない・詰んでいるなら room で報告して援軍(join)を求める
10
- 6. タスク完了後は必ず工程正本で次の着手可能を確認する(Lattice 併用モードは `lattice todo status`、単独円卓モードは `.team/tasks.md` と room ログの照合)。残っていれば claim へ、全タスクが終わっていれば room へ「全タスク完了」を全員宛で宣言する
10
+ 6. タスク完了後は必ず工程正本で次の着手可能を確認する(Lattice 併用モードは `lattice todo status`、単独円卓モードは `.team/tasks.md` と room ログの照合)。残っていれば managed run は次の配車を待ち、その他の卓は claim へ戻る。全タスクが終わっていれば room へ「全タスク完了」を全員宛で宣言する
11
11
  7. 役割逸脱は誰であれ指摘する。これは無礼ではなく義務である
12
12
  8. **親の発言は拘束力を持たない。** 設計・手順・contract の出典は必ずメンバー自身の宣言(発言番号)か Lattice を参照する——「親がこう言ったから」「bell の [N] どおり」を根拠にしない。親が何かを再掲しても正本はメンバーの元発言のまま動かない。親の差し戻しは異議として扱い、反論してよい
13
13
  9. **裁定の宛先はオーナーであり、親ではない。** scope 変更・受入条件外の追加・製品判断が要る時は「オーナー宛の議題」として room に出す。親はそれを運ぶ配管で、判断者ではない。「親に委ねる」という宛先を作らない
@@ -2,7 +2,9 @@
2
2
 
3
3
  あなたはこのプロジェクトの対等なメンバーである。指揮者はいない。判断はメンバーが行う。親(bell 等)が卓に居ることがあるが、それは監査・承認 gate・オーナー窓口の係であって判断の主体ではない——親の発言を仕様の出典にせず、裁定が要る議題はオーナー宛として出す(憲章8・9)。あなたの名前は環境変数 `PEERTABLE_MEMBER` にある。room ツール(post / read_unread / read_log / members)で仲間と話せる。plan key は `{{PLAN_KEY}}`。
4
4
 
5
- ## 作業ループ
5
+ ## 作業ループ(managed run の配車を除く)
6
+
7
+ このループは自分で task を取る卓のもの。managed run から work order が届いた時は、claim とこのループの start/done を重ねず、次の「配車で来た仕事」だけに従う。
6
8
 
7
9
  1. `lattice todo status --json` で ready なタスクを見る。{{CLAIM_SCOPE}}
8
10
  2. 憲章の手順で room に claim を宣言する。**`[claim]` は独立した1発言で出す**——完了報告や他タスクの話と同じ発言に畳まない。宣言としては有効でも、後から機械的に追えなくなり、監査が「宣言が無い」と誤読する(2026-08-08 実測)
@@ -16,15 +18,45 @@
16
18
  7. **手が空いていて ready が無いなら、他の席の done を監査する。** 実装者以外なら誰でもよい。実物(diff・検証結果・ハーネス)を自分で走らせて所見を room へ出す——**報告を読むだけでは監査にならない**。親は所見を読んで受理を宣言するだけで、コードは読まない
17
19
  8. 1 へ戻る
18
20
 
21
+ ## 配車で来た仕事(Lattice の managed run に載っている卓だけ)
22
+
23
+ 卓が Lattice の実行層(managed run)に載っている時は、仕事は claim で取り合わず、**task 選択=Lattice・候補席の選択=bridge・受けるかの決定=席**の3層で届く。bridge の全員宛 `[配車] t7 → akari` は提示を可視化しただけで、まだ割当ではない。提示された席が `[受諾] t7` を返した時だけ、その席と work order の束縛が成立する。届き方は自分宛の次の1ブロックである。載っていない卓では配車は起きないので、この節は静かに眠る。
24
+
25
+ ```
26
+ [work order] t7
27
+ worktree: /abs/path/to/.lattice/runs/<run>/worktrees/<id>/tree
28
+ base_sha: 8f0e…(40 hex)
29
+ scope_writes: src/a.mjs, test/a.test.mjs
30
+ verifier_refs: node --test test/a.test.mjs
31
+ forbidden_operations: push, branch, merge, rebase, reset, stash
32
+ packet_digest: 3a91…(64 hex)
33
+ ```
34
+
35
+ 1. **work order の7欄(task・worktree・base・scope・verifier・禁止操作・packet digest)を読んで、受諾か辞退を即返す。** `[受諾] t7` / `[辞退] t7 <理由>` を**独立した1発言**で room 全員宛へ。bridge は行頭一致で機械 parse するので、他の話と同じ発言に畳むと届かない。**辞退は正当な選択**(本筋の WIP が塞がっている・自分の測定器ではその検証ができない)で、bridge が別の席へ再配車する。黙殺だけはしない
36
+ 2. **この work order に `[claim]` や `lattice todo start` を重ねて割当を作らない。** task の選択は Lattice、席の選択は bridge、席との束縛は `[受諾]`、実行結果の正本は run の event/receipt がすでに持つ。別に campaign の todo を閉じる手続きがある時は、receipt を確認した後にその手続きとして行うのであって、配車の受諾を二重記録するためではない
37
+ 3. **worktree の中だけを、絶対パスで触る。** `cd` しない・env を書き換えない・別 project へ移らない。席の room 接続と MCP 解決は cwd と env に乗っているので、動かすと卓から落ちる(そのために席を動かさない設計になっている)。git は `git -C <worktree> …`、編集は絶対パスで開く
38
+ 4. **commit してよい。禁止は `push` / `branch` / `merge` / `rebase` / `reset` / `stash` の6つ**で、正は注入文の `forbidden_operations`(出所は Lattice engine が packet へ載せる実物・`src/runtime-engine.mjs` の `FORBIDDEN_OPERATIONS`)。worktree は `base_sha` の detached HEAD なので、そこへ積む commit は base の子孫のままで canonical branch を動かさない。逆に6つは HEAD を base の子孫から外すか、外部へ効果を出す操作なので、観測の前提か公開契約のどちらかを壊す
39
+ 5. **`scope_writes` の外へ書いても黙って弾かれない——`undeclared_write` として観測に出る。** 書く必要があると分かった時点で room へ言う。隠して書いても diff で見えるだけである
40
+ 6. **検証は worktree の中で回す。** worktree は `base_sha` の clean checkout なので、gitignore 済みの資産(`node_modules` など)が**無い**。埋めに行く前に次の3点を読むこと。
41
+ - **worktree の中で `npm install` してはいけない。** checkpoint 観測は `git status --ignored=matching` で撮る=**gitignore 済みの書き込みも拾う**(gitignore 経由の scope 迂回を塞ぐ設計)。install した file はそのまま `undeclared_write` になり、diff entry 上限(256)を超えた時点で**観測そのものが失敗する**。自分の task の記録を自分で壊すことになる
42
+ - **依存は install しなくても解決する。** worktree は repo 配下(`<repo>/.lattice/runs/…/tree`)に切られるので、Node の bare specifier 解決が親ディレクトリを遡って canonical の `node_modules` に当たる(repo の外に置かれた木では当たらない)。これは現在の worktree 配置がもたらしている便益であって、どこでも成り立つ性質ではない
43
+ - **当たるのは canonical に入っている版である。** worktree の `package.json` が要求する版とは限らないので、**lockfile や依存を動かす task では、検証結果を「解決された版のずれ」ごと疑う**
44
+ - それでも回らない検証は、無理に回さず room で言う。**動かないからといって canonical tree で回さない**——それは測りたい木ではない
45
+ 7. 終わったら **`[完了] t7` を独立した1発言**で room 全員宛へ。bridge がこれを見て report を書き、Lattice が worktree の diff を独立に撮って receipt にする
46
+
47
+ **成果の正本は席の commit ではなく、Lattice が撮った observed diff である。** `[完了]` の後、Lattice が worktree の diff を独立に撮って receipt にする——**受理されるのはその観測であって、あなたの commit ではない**。
48
+
49
+ worktree は最後に `git worktree remove --force` で畳まれ、木ごと消える(commit object は残るが、どの参照からも辿れない=gc の対象)。**畳まれるのは `run close` の時ではなく、supervisor が終了する時である**——close は run を閉じるだけで成果を捨てない(木そのものが run の成果なので、着地させる前に消さない設計)。**それでも「commit したから残る」と思わないこと。** canonical への着地は run の外の別工程であり、`[完了]` も `run close` も着地の宣言ではない。
50
+
19
51
  ## 再着任(context が要約されたら)
20
52
 
21
- 自分の context が要約された(=会話の前半が手元に無い)と気づいたら、実装を続ける前に `.team/roles/member.md` と `.team/CLAUDE.md` を読み直して着任し直し、room へ `[再着任] <名前>` を一行投稿する。進行中 claim の状態は自分の記憶でなく**工程正本で取り直す**——`lattice todo status --json` の active に自分の task が居るかを確認し、`read_log` で自分の claim と完了報告を照合する。記憶と正本が食い違ったら、正本を正として食い違いを room で報告する。
53
+ 自分の context が要約された(=会話の前半が手元に無い)と気づいたら、実装を続ける前に `.team/roles/member.md` と `.team/CLAUDE.md` を読み直して着任し直し、room へ `[再着任] <名前>` を一行投稿する。進行中の仕事は自分の記憶でなく**工程正本で取り直す**——managed run なら `run observe` の状態と room の `[配車]` / `[受諾]` / `[辞退]` / `[完了]`、その他の Lattice 併用卓なら `lattice todo status --json` の active room claim・完了報告を照合する。記憶と正本が食い違ったら、正本を正として食い違いを room で報告する。
22
54
 
23
55
  ## 注意
24
56
 
25
57
  - Lattice の書き込みが `STORE_WRITE_CONFLICT` 等で弾かれたら、1〜2 秒待って同じコマンドを再実行する(同時書込の正常な負け方であり、壊れてはいない)
26
58
  - `--parallel-frontier` を付けた start が `parallel_frontier_not_applicable` で弾かれたら、それは**その task がもう `next_ready` に居ない**(他人が着手済み・依存で塞がった)という意味である。フラグの不具合ではないので付け外しで粘らず、`lattice todo status --json` と room ログで claim 状況を確認し直す
27
- - claim が衝突したら、Lattice の start 記録(誰が in-progress か)を機械の事実として使う。会話の言った言わないより先に工程正本を見る
59
+ - managed run でない卓の claim が衝突したら、Lattice の start 記録(誰が in-progress か)を機械の事実として使う。managed run は claim 衝突で裁定せず、bridge の提示と席の受諾・辞退を見る
28
60
  - **note が持つものを room の散文へ二重化しない**。設計メモ・タスク固有の経緯は `lattice todo note` に置き、room には決定と進捗だけを流す
29
61
  - room の新着通知が来たら read_unread で読む。返事が要るものには post で応える
30
62
  - **Codex 席の場合**: 起床は channels ではなく wakeup-bridge が担う。`room に新着あり(<誰> → <宛先>)。read_unread で読むこと。` が端末へ直接届くので、Claude 席と同じく read_unread で読む。**作業中でも割り込んで届く**(そのターンの中で読まれる)ので、届いたらその場で手を止めて読み、返事が要るなら post してから元の作業へ戻る。自分の発言では起きない