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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.74.0 — 2026-10-03

### runtime errorの送信をLattice自身が持つ(ADR 0193)

- Latticeが、未受領のruntime error記録をBugHubの製品報告の受け口へ自分で送る。これまでは工場
(dotagents)のreportが運んでいた。
- **既定では通信しない。** 送るのは、端末で `lattice runtime-errors reporting enable --json` を打ち、
BugHubの持ち主が合鍵のfile(`~/.config/bughub/product-credentials/lattice.json`)を置いた端末だけ。
どちらかが欠ければnetworkへ触れない。設定を変えていない端末の動きは今までと同じ。
- 新しいcommand:
- `lattice runtime-errors reporting <status|enable|disable> --json`
- `lattice runtime-errors report --json`(未受領の分を今送る。受領まで済むか、送るものが無ければexit 0)
- 秘密は通信に載せず、送るバイト列と時刻へのHMAC-SHA256署名だけを付ける。受領済みにするのは、
署名つきの200が返った時だけ。届いたか分からない時は未受領のまま残し、後から送り直す。
- 送る時機: 故障を記録した直後と、以後のCLI実行(`hooks` を除く)の終わりに、切り離した子processで送る。
CLIの応答は待たせない。1分に1回まで、同じ中身の送り直しは1時間に1回まで。
- 送信を有効にした端末では、工場の設定が無くても収集が有効になる。
- `LATTICE_RUNTIME_ERROR_REPORTING=0` は、既定の置き場の送信設定を読まない(試験と自動化の口)。
- Windowsは収集に対応しないので、送信も `unsupported` と答える。

## 0.73.0 — 2026-10-03

### runtime errorの収集: 対応しないOSは `unsupported` と答える
Expand Down
35 changes: 34 additions & 1 deletion bin/lattice.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,29 @@ if (help !== null) {
}
}

await pendingObservation;
await scheduleRuntimeErrorReport();

/**
* 未受領のruntime error記録を、切り離した子processで送らせる(ADR 0193)。このCLIの応答は待たせない。
* 送信を有効にしていない端末では、設定fileの有無を1回見るだけで終わる。hookは打鍵ごとに走るので見ない。
*/
async function scheduleRuntimeErrorReport() {
if (help !== null || args[0] === '--version' || args[0] === 'hooks'
|| (args[0] === 'runtime-errors' && args[1] === 'report')) return;
try {
const { runtimeErrorReportingEnabled } = await import('../src/runtime-errors.mjs');
if (!runtimeErrorReportingEnabled()) return;
const { runtimeErrorAutoReportDue } = await import('../src/runtime-error-reporting.mjs');
if (!runtimeErrorAutoReportDue()) return;
const { spawn } = await import('node:child_process');
const { tmpdir } = await import('node:os');
const { fileURLToPath } = await import('node:url');
spawn(process.execPath, [fileURLToPath(import.meta.url), 'runtime-errors', 'report', '--auto', '--json'],
{ cwd: tmpdir(), detached: true, stdio: 'ignore', windowsHide: true }).unref();
} catch { /* 送信の予約に失敗しても、打たれたcommandの結果は変えない。 */ }
}

// typed契約の外へ漏れた例外を、opt-inのruntime error記録へ残す。どの面で・どの種類の例外で
// 落ちたかを固定語彙の分類(safe_context)で添える——error_codeだけでは原因を追えなかった。
function observeInternalFailure(error, commandKind) {
Expand All @@ -199,7 +222,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> --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>> --json' })}\n`);
return 2;
};
const options = { version: packageJson.version };
Expand Down Expand Up @@ -228,6 +251,16 @@ 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 && ['status', 'enable', 'disable'].includes(words[1])) {
const reporting = await import('../src/runtime-error-reporting.mjs');
result = words[1] === 'status' ? reporting.runtimeErrorReportingStatus(options)
: reporting.setRuntimeErrorReporting(words[1] === 'enable', options);
} else if (words[0] === 'report' && (words.length === 1 || (words.length === 2 && words[1] === '--auto'))) {
// 送れなかった時も結果は1行で返す。受領まで済んだ時と、送るものが無い時だけexit 0。
const reporting = await import('../src/runtime-error-reporting.mjs');
result = await reporting.reportRuntimeErrors({ ...options, auto: words.length === 2 });
process.stdout.write(`${JSON.stringify(result)}\n`);
return ['delivered', 'nothing_pending'].includes(result.outcome) ? 0 : 1;
} else {
return usage();
}
Expand Down
21 changes: 19 additions & 2 deletions docs/01_integration-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,27 @@ native Windowsでは`HOST_PLATFORM_UNSUPPORTED`を返し、設定やstateへ書
check 5本・overall failed→exit 1・read-only・秘密なし)。正典は`src/factory-diagnostics.mjs`
- runtime error store: `lattice runtime-errors <snapshot|ack|diagnostics|resolve|reopen|compact> --json`
(schema `lattice.runtime_errors.v1`。Caveat同型の工場契約)。**opt-in**=工場共有config
`~/.config/dotagents/factory-reporter.json`の`collection.enabled`のみが収集を有効化し、
reporting(BugHub送信)はdotagents adapter所有で本storeは外部送信しない(collection/reporting分離)。
`~/.config/dotagents/factory-reporter.json`の`collection.enabled`か、Lattice自身の送信設定(下)の
どちらかが有効な時だけ収集する。storeそのものは外部送信しない。
固定catalog 5 code・fingerprint集約・cursor/ack・resolved+ack済み30日compact・POSIX owner-only検査で
fail closed。正典は`src/runtime-errors.mjs`
- runtime errorの送信(ADR 0193): `lattice runtime-errors report --json`と
`lattice runtime-errors reporting <status|enable|disable> --json`。Lattice自身が、未受領の記録を
BugHubの製品報告の受け口へ送る。正典は`src/runtime-error-reporting.mjs`
- **既定では通信しない。** `reporting enable`を打った端末(設定は
`${XDG_CONFIG_HOME:-~/.config}/lattice/runtime-error-reporting.json`)で、BugHubの持ち主が置いた合鍵のfile
(`~/.config/bughub/product-credentials/lattice.json`、本人所有・0600・symlinkでない)がある時だけ送る。
dotagentsの設定は読まない。宛先は合鍵のfileの`url`。
- 秘密は通信に載せない。`Authorization: BugHub-HMAC-SHA256 key_id=…, ts=…, sig=…`
(`sig = HMAC-SHA256(secret, ts + "\n" + SHA-256(送るバイト列))`)。
- 本文は`schema_version`・`report_id`・`product_id`・`installed_version`・`observed_at`・`runtime_errors`・
`resolutions`の7項目で、各記録は`snapshot`が出す項目のまま。端末名は入れない。
- 受領済み(storeのack)にするのは、200・`accepted: true`・`report_id`一致・応答の署名一致がそろった時だけ。
そろわなければ未受領のまま残し、後から新しい`report_id`でその時点の累計を送り直す。
- 送る時機: 故障を記録した直後と、以後のCLI実行(`hooks`を除く)の終わりに、切り離した子processで送る。
1分に1回まで、同じ中身の送り直しは1時間に1回まで。手で打つ`report`はこの制限を見ない。
- `LATTICE_RUNTIME_ERROR_REPORTING=0`は、既定の置き場の送信設定を読まない(試験と自動化の口)。
- Windowsは収集に対応しないので、送信も`unsupported`と答える。
- `diagnostics.collection`は`enabled`・`disabled`・`unsupported`の3値。`unsupported`は「このOSでは収集に
対応しない」という製品の答えで、Windowsが返す(storeの所有者と権限をPOSIXの形で確かめられない)。
設定が有効でも記録は作らない。`status`・`cursor`・配列・`diagnostics`のキーは`disabled`の時と同じ形
Expand Down
59 changes: 59 additions & 0 deletions docs/adr/0193-product-owned-runtime-error-reporting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ADR 0193: runtime errorの送信をLattice自身が持つ

- Status: accepted
- Date: 2026-10-03
- Supersedes: runtime error storeの「reporting(BugHub送信)はdotagents adapter所有」
(`docs/01_integration-package.md` 5.5、`src/runtime-errors.mjs`冒頭)

## Context

Latticeのruntime error記録は、工場(dotagents)のfactory reporterが`runtime-errors snapshot`を読み、
工場のreportに載せてBugHubへ運んでいた。Lattice自身は外部へ送らなかった。

オーナーの裁定(2026-10-03)で責務が変わった。エラーを上げるのは各プロダクトの責務で、
工場が持つのは工場の方針と、プロダクト・パッケージの管理だけである。BugHubは、端末に入る
CLI製品が自分の分だけを送る受け口(製品報告)を用意する。この受け口はオーナーの端末だけが使い、
LANの中に置く。LatticeはOSSなので、外の利用者の端末からは送らない。

`AGENTS.md`は、Latticeの設定とstateはこのrepoだけを正本とし、dotagentsは実行条件ではないと定めている。
収集の有効化が工場の設定fileだけに依っている点は、この原則からも外れていた。

## Decision

1. Latticeは、未受領のruntime error記録をBugHubの製品報告の受け口へ自分で送る。
入口は`lattice runtime-errors report --json`、実装は`src/runtime-error-reporting.mjs`。
2. **既定では通信しない。** 送るのは、端末で`lattice runtime-errors reporting enable --json`を打ち
(設定は`${XDG_CONFIG_HOME:-~/.config}/lattice/runtime-error-reporting.json`)、かつBugHubの持ち主が
合鍵のfile(`~/.config/bughub/product-credentials/lattice.json`)を置いた端末だけである。
どちらかが欠ければnetworkへ触れない。dotagentsの設定は、送信の判断に使わない。
3. 送信設定が有効な端末では、工場の設定が無くても収集を有効にする。工場の設定による収集の有効化は残す
(工場のreportが運ぶ経路を、移行が済むまで壊さない)。
4. 合鍵のfileは、本人所有・0600・symlinkでない通常fileで、`url`・`key_id`・`secret`の3項目だけを持つ形に限る。
それ以外は使わず、理由を返す。秘密は通信・結果・記録のどこにも写さない。
5. 秘密は通信に載せない。送ったバイト列のSHA-256と時刻へのHMAC-SHA256署名を`Authorization`に付ける。
宛先は平文のHTTPで、持ち歩く端末が外のnetworkで同じaddressの別の機器へ送っても、秘密は漏れない。
6. 受領済みにするのは、200・`accepted: true`・`report_id`一致・応答の署名一致がそろった時だけである。
受領済みの印はstoreの`acknowledged_through`を使う。そろわない時は「届いたか不明」として未受領のまま残し、
後から新しい`report_id`でその時点の累計を送り直す(BugHubは同じ回数・同じ最終時刻を二重に数えない)。
7. 送る時機はLatticeが決める。故障を記録した直後と、以後のCLI実行の終わりに、切り離した子processで送る。
CLIの応答は送信を待たない。`hooks`(打鍵ごとに走る)は時機に数えない。自動送信は1分に1回まで、
同じ中身の送り直しは1時間に1回までとする。手で打つ`report`はこの制限を見ない。
8. 本文に載せるのは、`runtime-errors snapshot`が出す記録の項目だけである。端末名は載せない
(BugHubが合鍵から解決する)。
9. Windowsは収集に対応しない(`collection: unsupported`)。送信も`unsupported`と答える。

## Consequences

- 工場のreportとLatticeの両方から同じ記録が届く期間がある。BugHubの同一性は端末・製品・fingerprintで、
二重にはならない。Latticeが送り始めたら、工場のreportからLatticeの分を外す。
- 受領済みの印を工場のackと共有する。どちらのackも「BugHubへ届いた」を意味する。
- Windowsの端末からは送れない。Windowsでstoreを安全に置く方法が決まるまで残る不具合である。
- 試験と自動化は`LATTICE_RUNTIME_ERROR_REPORTING=0`で既定の置き場の送信設定を読まない。CIのrunnerは
利用者の本物のHOMEで走るので、製品試験の環境はこの値を必ず持つ。

## Acceptance

- 署名と応答の署名が、BugHubの契約の試験値と一致する。
- 送信を有効にしていない端末と、合鍵の無い端末は、受け口へ1回も接続しない。
- 署名の合わない200・別の`report_id`・`accepted: false`・5xx・時間切れでは、storeのackが進まない。
- 送信を有効にした端末で故障を1件起こすと、CLIが返った後に子processが届け、ackが進む。
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.73.0",
"version": "0.74.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 scripts/run-product-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ export function productTestEnvironment(parentEnv = process.env) {
// sensor init用WASM poolまで最大8本prewarmするとnested oversubscriptionになり、
// Windowsで複数の子processが0xC0000005になった。sensor自身の並列契約は
// 独立したtest:sensor gateが検証するため、このharness内だけsingle-workerにする。
// runnerは利用者の本物のHOMEで走る。送信を有効にした端末でも、試験がその設定を拾ってBugHubへ送らない。
const env = { ...parentEnv, LATTICE_DASHBOARD_AUTOSTART: '0',
LATTICE_SENSOR_PARSE_WORKERS: '1' };
LATTICE_SENSOR_PARSE_WORKERS: '1', LATTICE_RUNTIME_ERROR_REPORTING: '0' };
delete env.FORCE_COLOR;
return env;
}
Expand Down
4 changes: 4 additions & 0 deletions src/cli-help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ Commands:
resolve <fingerprint>
reopen <fingerprint>
compact
report 未受領の記録をBugHubの受け口へ送る(送信を有効にした端末だけ)
reporting <status|enable|disable>
`,
bridge: `Usage: lattice bridge <command> [options] --json

Expand Down Expand Up @@ -312,6 +314,8 @@ const SUBCOMMAND_USAGE = Object.freeze({
'runtime-errors resolve': 'runtime-errors resolve <fingerprint> --json',
'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',
'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