Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.76.0 — 2026-10-03

### 修正: 故障や解決の直後の送信が、受け口に断られていた(ADR 0195)

- 0.74.0・0.75.0では、報告の `observed_at` を秒へ切り捨てて作っていた。BugHubは、記録の時刻が
`observed_at` より後の報告を断る。記録と同じ1秒の中で送ると `observed_at` が記録より前になり、
422 `invalid_report` で断られた。自動送信は記録の直後に走るので、ほとんどの場合これに当たっていた。
- 断られた報告は、最大1時間遅れて送り直されていた。記録は失われていない。
- `observed_at` を、送る時刻と、載せる記録の時刻のうち最も後の時刻にした。直後の送信が最初の1回で届く。

### 送信の経路を確かめる `reporting verify`

- `lattice runtime-errors reporting verify --json` を足した。記録を載せない空の報告を1通送り、合鍵・署名・
受け口までの経路・受領の署名を確かめる。確かめられた時だけ `verified`・exit 0。
- storeの記録と受領の印には触れない。受け口は受領を記録するだけで、issueも通知も動かさない。
- 送信を有効にしていない端末と、合鍵の無い端末では、networkへ触れない。
- 受け口は端末×製品ごとに1分に1回まで受ける。`report` と続けて打つ時は1分空ける。

## 0.75.0 — 2026-10-03

### runtime errorの収集と送信がWindowsで動く(ADR 0194)
Expand Down
11 changes: 9 additions & 2 deletions bin/lattice.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@ await scheduleRuntimeErrorReport();
* 送信を有効にしていない端末では、設定fileの有無を1回見るだけで終わる。hookは打鍵ごとに走るので見ない。
*/
async function scheduleRuntimeErrorReport() {
// `reporting verify`の直後は送らない。受け口は1分に1回まで受けるので、続けて送ると断られる。
if (help !== null || args[0] === '--version' || args[0] === 'hooks'
|| (args[0] === 'runtime-errors' && args[1] === 'report')) return;
|| (args[0] === 'runtime-errors' && (args[1] === 'report' || (args[1] === 'reporting' && args[2] === 'verify')))) return;
try {
const { runtimeErrorReportingEnabled } = await import('../src/runtime-errors.mjs');
if (!runtimeErrorReportingEnabled()) return;
Expand Down Expand Up @@ -222,7 +223,7 @@ function workingDirectory() {
async function runRuntimeErrorsCli(rest) {
const runtimeErrors = await import('../src/runtime-errors.mjs');
const usage = () => {
process.stderr.write(`${JSON.stringify({ schema: 'lattice.cli_error.v2', code: 'USAGE', message: 'usage: lattice runtime-errors <snapshot [--after-cursor N] [--limit N]|ack <cursor>|diagnostics|resolve <fingerprint>|reopen <fingerprint>|compact|report|reporting <status|enable|disable>> --json' })}\n`);
process.stderr.write(`${JSON.stringify({ schema: 'lattice.cli_error.v2', code: 'USAGE', message: 'usage: lattice runtime-errors <snapshot [--after-cursor N] [--limit N]|ack <cursor>|diagnostics|resolve <fingerprint>|reopen <fingerprint>|compact|report|reporting <status|enable|disable|verify>> --json' })}\n`);
return 2;
};
const options = { version: packageJson.version };
Expand Down Expand Up @@ -251,6 +252,12 @@ async function runRuntimeErrorsCli(rest) {
result = runtimeErrors.setRuntimeErrorStatus(words[1], words[0] === 'resolve' ? 'resolved' : 'open', options);
} else if (words[0] === 'compact' && words.length === 1) {
result = runtimeErrors.compactRuntimeErrors(options);
} else if (words[0] === 'reporting' && words.length === 2 && words[1] === 'verify') {
// 空の報告を1通送って経路を確かめる。受領まで確かめられた時だけexit 0。
const reporting = await import('../src/runtime-error-reporting.mjs');
result = await reporting.verifyRuntimeErrorReporting(options);
process.stdout.write(`${JSON.stringify(result)}\n`);
return result.outcome === 'verified' ? 0 : 1;
} else if (words[0] === 'reporting' && words.length === 2 && ['status', 'enable', 'disable'].includes(words[1])) {
const reporting = await import('../src/runtime-error-reporting.mjs');
result = words[1] === 'status' ? reporting.runtimeErrorReportingStatus(options)
Expand Down
7 changes: 6 additions & 1 deletion docs/01_integration-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ native Windowsでは`HOST_PLATFORM_UNSUPPORTED`を返し、設定やstateへ書
`%LOCALAPPDATA%\Lattice\runtime-errors\runtime-errors.json`で、このフォルダは継承を切って本人・SYSTEM・
Administratorsだけに絞る。他のaccountが触れる形で中身があるフォルダは、絞らずに止める。
- runtime errorの送信(ADR 0193): `lattice runtime-errors report --json`と
`lattice runtime-errors reporting <status|enable|disable> --json`。Lattice自身が、未受領の記録を
`lattice runtime-errors reporting <status|enable|disable|verify> --json`。Lattice自身が、未受領の記録を
BugHubの製品報告の受け口へ送る。正典は`src/runtime-error-reporting.mjs`
- **既定では通信しない。** `reporting enable`を打った端末(設定は
`${XDG_CONFIG_HOME:-~/.config}/lattice/runtime-error-reporting.json`)で、BugHubの持ち主が置いた合鍵のfile
Expand All @@ -246,6 +246,11 @@ native Windowsでは`HOST_PLATFORM_UNSUPPORTED`を返し、設定やstateへ書
(`sig = HMAC-SHA256(secret, ts + "\n" + SHA-256(送るバイト列))`)。
- 本文は`schema_version`・`report_id`・`product_id`・`installed_version`・`observed_at`・`runtime_errors`・
`resolutions`の7項目で、各記録は`snapshot`が出す項目のまま。端末名は入れない。
- `observed_at`は、送る時刻と、載せる記録の`last_seen`・`resolved_at`のうち最も後の時刻(ADR 0195)。
BugHubは、記録の時刻が`observed_at`より後の報告を断る。秒へ切り捨てない。
- `reporting verify`は、記録を載せない空の報告を1通送って、合鍵・署名・受け口までの経路を確かめる。
storeの記録と受領の印には触れない。受け口は受領を記録するだけで、issueも通知も動かさない。
受領まで確かめられた時だけ`verified`・exit 0。受け口は端末×製品ごとに1分に1回まで受ける。
- 受領済み(storeのack)にするのは、200・`accepted: true`・`report_id`一致・応答の署名一致がそろった時だけ。
そろわなければ未受領のまま残し、後から新しい`report_id`でその時点の累計を送り直す。
- 送る時機: 故障を記録した直後と、以後のCLI実行(`hooks`を除く)の終わりに、切り離した子processで送る。
Expand Down
3 changes: 2 additions & 1 deletion docs/adr/0193-product-owned-runtime-error-reporting.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# ADR 0193: runtime errorの送信をLattice自身が持つ

- Status: accepted(Decision 9 と、Consequences の「Windowsの端末からは送れない」は
[ADR 0194](0194-runtime-error-store-on-windows.md) が置き換える)
[ADR 0194](0194-runtime-error-store-on-windows.md) が置き換える。`observed_at`の作り方と`reporting verify`は
[ADR 0195](0195-report-observation-time-and-verify.md) が足す)
- Date: 2026-10-03
- Supersedes: runtime error storeの「reporting(BugHub送信)はdotagents adapter所有」
(`docs/01_integration-package.md` 5.5、`src/runtime-errors.mjs`冒頭)
Expand Down
50 changes: 50 additions & 0 deletions docs/adr/0195-report-observation-time-and-verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# ADR 0195: 報告の観測時刻は記録より前にしない。経路は空の報告で確かめる

- Status: accepted
- Date: 2026-10-03
- Amends: [ADR 0193](0193-product-owned-runtime-error-reporting.md)(送信の本文の作り方と、送信のcommand)

## Context

ADR 0193の送信を4台で有効にした日に、2つのことが分かった。

1. **故障や解決の直後の自動送信が、受け口に断られる。** Latticeは`observed_at`を、署名の時刻(秒)と同じ値へ
切り捨てて作っていた。BugHubは、記録の`last_seen`や解決の`resolved_at`が`observed_at`より後の報告を
422 `invalid_report`で断る。記録と同じ1秒の中で送ると、切り捨てた`observed_at`は記録より前になる。
自動送信は記録の直後に走るので、ほとんどの場合これに当たる。断られた報告は、同じ中身の送り直しの制限で、
最大1時間遅れて届く。記録は失われない。最初の到達確認が通ったのは、記録が秒の終わり(.999)で、
送信が次の秒へまたいだためだった。
2. **Windowsの端末から届くことを、確かめる方法が無い。** 届くことを確かめるには記録が1件要る。
起きていない故障の記録を作って送れば、受け口の履歴に偽の故障が残り、持ち主へ通知が出る。
合鍵を入れ替えた時も、次の本物の故障まで、新しい合鍵で届くかが分からない。

## Decision

1. `observed_at`は、送る時刻と、載せる記録の`last_seen`・`resolved_at`のうち、最も後の時刻にする。
秒へ切り捨てない。署名の時刻`ts`は今までどおり送る時刻の秒で、`observed_at`との差は、端末の時計が
正しければ1秒に満たない。端末の時計が戻って記録のほうが後になった時は、`observed_at`を記録の時刻まで進める
——差がBugHubの許す幅(10分)を越えれば、受け口が時計のずれとして断る。
2. 受け口の検査は変えない。「記録の時刻は`observed_at`以前」はBugHubの契約で、正しい。
3. `lattice runtime-errors reporting verify --json`を足す。記録を載せない空の報告を1通送り、
合鍵の読み取り・署名・受け口までの経路・受領の署名を確かめる。
- 送る条件は`report`と同じ。送信を有効にしていない端末と、合鍵の無い端末は、networkへ触れない。
- storeの記録、受領の印(`acknowledged_through`)、送る時機の記録には触れない。
- 結果は`verified`(200・`accepted: true`・`report_id`一致・応答の署名一致がそろった時だけ)か、
`report`と同じ語(`rejected`・`unconfirmed`・`disabled`等)。`verified`の時だけexit 0。
- 受け口は、空の報告の受領を記録するだけで、issueも通知も動かさない。
4. `reporting verify`の後のCLI実行の終わりでは、自動送信を起こさない。受け口は端末×製品ごとに
1分に1回まで受けるので、続けて送ると断られる。

## Consequences

- 故障や解決の直後の自動送信が、最初の1回で届く。
- `reporting verify`は受け口の1分に1回の枠を使う。その直後1分以内に故障が起きると、自動送信は429で断られ、
1時間後(または次の記録の時)に送り直される。
- 手で打つ`report`と`reporting verify`を続けて打つ時は、1分空ける。

## Acceptance

- 記録や解決と同じ1秒の中で送った報告を、BugHubと同じ規則で検査する受け口が受領する。
- 端末の時計が戻った時、`observed_at`は記録の時刻になる。
- `reporting verify`は空の配列だけを載せ、確かめる前後でstoreの診断と送信の状態が変わらない。
- 送信を有効にしていない端末と合鍵の無い端末では、`reporting verify`が受け口へ1回も接続しない。
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@quolu/lattice",
"version": "0.75.0",
"version": "0.76.0",
"description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
"author": {
"name": "Quo / クオ at kitepon.dev",
Expand Down
3 changes: 2 additions & 1 deletion src/cli-help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Commands:
compact
report 未受領の記録をBugHubの受け口へ送る(送信を有効にした端末だけ)
reporting <status|enable|disable>
reporting verify 空の報告を1通送り、合鍵と受け口までの経路を確かめる(記録には触れない)
`,
bridge: `Usage: lattice bridge <command> [options] --json

Expand Down Expand Up @@ -315,7 +316,7 @@ const SUBCOMMAND_USAGE = Object.freeze({
'runtime-errors reopen': 'runtime-errors reopen <fingerprint> --json',
'runtime-errors compact': 'runtime-errors compact --json',
'runtime-errors report': 'runtime-errors report --json',
'runtime-errors reporting': 'runtime-errors reporting <status|enable|disable> --json',
'runtime-errors reporting': 'runtime-errors reporting <status|enable|disable|verify> --json',
'bridge setup': 'bridge setup --listen <IP> [--port <49152..65535|auto>] [--dashboard|--upstream <URL>] [--hub <URL>|none] [--allow-host <host>...] --json',
'bridge reconfigure': 'bridge reconfigure [--listen <IP>] [--port <49152..65535|auto>] [--dashboard|--upstream <URL>] [--hub <URL>|none] [--allow-host <host>...] --json',
'bridge status': 'bridge status --json',
Expand Down
Loading
Loading