Skip to content

fix(advisory): add a recovery verb for schema 1 advisory choice stores - #2392

Merged
j5ik2o merged 5 commits into
mainfrom
bolt/fix-2330-advisory-store-recovery
Aug 7, 2026
Merged

fix(advisory): add a recovery verb for schema 1 advisory choice stores#2392
j5ik2o merged 5 commits into
mainfrom
bolt/fix-2330-advisory-store-recovery

Conversation

@j5ik2o

@j5ik2o j5ik2o commented Aug 7, 2026

Copy link
Copy Markdown
Member

概要

Refs #2330 / #2385(Bolt 2 — intent 260807-failclosed-recovery-path、scope self-fix)

advisory choice store の schema 1→2 回復経路不在(pending 実在 intent の stage report 恒久ブロック)を、parseStore 無変更のまま回復 verb の追加で是正する(#2385 Q3 裁定 = #2318 の「schema 1 を翻訳しない」設計の保存)。

変更

検証(§12a 2イテレーション READY)

  • TDD Red 実測5件(assertion 実文は intent record の code-summary 参照)/ 落ちる実証2件(pending ガード・receipts ガードの独立赤)
  • NFR-5 の allowlist 3点セット実測: 591 エントリ resolve failures 0 / reason 直読照合 19/19 一致 / span 膨張 0(census は最終 base edfee5818)
  • typecheck 0 / lint 0 / advisory 関連 180 tests 0 fail / docs ガード 133 tests 0 fail / complexity 0 / build 後追跡ファイル不変 / source-only 0 / no-silent-drop gate NO_SILENT_DROP_OK
  • 再接地済み(base = edfee5818、競合ゼロ — merge-tree 非破壊プローブで事前確認)

クローズ条件

着地後、schema 1 store を持つ実 intent での回復 → report ブロック解消の実測を経て #2330 をクローズする。


Note

Medium Risk
Recovery mutates authoritative advisory-choice evidence under the audit lock and intentionally discards legacy receipts, so wrong intent targeting or operator misuse could reopen advisories or reset formal-check attempt numbering; guards and loud refusal on foreign intent mitigate but the path is operational and security-adjacent for checkpoint holds.

Overview
Intents stuck on schema 1 .amadeus-advisory-choice.json stores can no longer be cleared by normal choices because readers fail closed on the old shape. This PR adds an explicit migration exit while keeping parseStore and ADR-9’s “do not translate schema 1 receipts” rule unchanged.

recover-schema-1 in amadeus-advisory-choice.ts runs under the audit lock on a single store (--project-dir, default active intent). It reads schema 1 JSON directly (not parseStore), re-parses pending rows via existing parsePending, drops all legacy receipts (no provenance-union guessing), and writes schema 2 with empty receipts. Open advisories become unanswered again so checkpoints can re-prompt humans—the same outcome the invalid-store hold intended, but now reachable.

Safety: recovery refuses without writing if the store’s intent run (from pending, or from receipt identities when pending is empty) does not match the active intent. Success JSON reports receipts_dropped, re_presentation_required, and formal_check_attempts_reset (run-now receipt count only).

Docs (12-state-machine, audit-format) document schema 2, the provenance union, and this verb. t470 integration tests cover salvage, foreign-intent refusal, receipts-only stores, CLI/spawn wiring, and post-recovery advisoryReportHoldReason behavior; coverage allowlist notes the thin import.meta.main dispatch arm.

Reviewed by Cursor Bugbot for commit 82d5235. Configure here.

Summary by CodeRabbit

  • 新機能

    • アドバイザリ選択の証跡に、人的判断または自動判断の情報を記録できるようになりました。
    • 旧形式のストアを復旧する recover-schema-1 コマンドを追加しました。
    • 保留中のアドバイザリを保持し、旧証跡を安全に破棄して新形式へ移行できます。
  • 改善

    • 互換性のない旧形式、対象外のストア、破損データは自動変換せず、安全側に停止します。
    • 復旧結果として、破棄件数、再提示の要否、チェック試行回数のリセット状況を報告します。

j5ik2o added 3 commits August 7, 2026 15:27
An intent whose advisory choice store predates the schema 2 migration had no
way forward. ADR-9 deliberately refuses to translate a schema 1 store — a
`humanTurn`-only receipt has no meaning under the provenance union, and the
safe answer is to ask the human again — so every reader falls closed to a
hold. That refusal is right, but on its own it is terminal: `report` answers
`advisory choice evidence is invalid: ...` and no human answer can clear it,
because the store the answer would be written to is the unreadable one.

`recover-schema-1` is the way out. It leaves parseStore untouched, salvages the
pending advisories through the unchanged parsePending (pending has been schema
1 all along and did not migrate), discards the schema 1 receipts rather than
translating them, and writes a schema 2 store. Discarding is the point rather
than a cost: an advisory with no receipt is one the checkpoint asks again,
which is the "ask the human again" the hold already stood for.

It operates on one store — the active intent's, or the one --project-dir names
— and refuses loudly, changing nothing, when the salvaged pending belongs to
another intent, so a store reached through a stale intent cursor is never
emptied by accident. The outcome names the receipts dropped, whether
re-presentation is required, and the formal-check attempt counts that reset
with the discarded run-now receipts.

All logic lives in an exported seam that t470 drives in-process; the module
dispatch arm is one call plus process.exit, spawn-verified by t470 and
allowlisted for the patch gate. t458's schema 1 refusal pin is unchanged.

Refs #2330
t470 spawns amadeus-advisory-choice.ts to prove the recover-schema-1 dispatch
arm is really wired, which makes it a none->cli member of the mechanism honesty
ratchet. The ledger is a deliberate manual pin: a new spawning test must not
change the cli surface without a human edit, so the file is named here rather
than the assertion relaxed.

Refs #2330
The misdirection check read the intent run off the salvaged pending rows only,
which left it vacuous in the one case where the entire content of the store is
about to be discarded: a schema 1 store holding receipts and no pending. A
stale intent cursor could therefore reach another intent's receipts-only store
and empty it, which is the accident the check exists to prevent.

The receipts carry an identity too, so the check now falls back to reading the
intent run off them when no pending row carries it. This is a safety read, not
a translation: nothing about what the receipt MEANT is interpreted — ADR-9's
refusal stands — and a receipt too malformed to show an intent run yields
nothing, so an unreadable receipt can only withhold a refusal, never grant a
permission.

The same-intent receipts-only path is unchanged: it still recovers to a schema
2 store with empty pending and reports the receipts it dropped.

Refs #2330
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@j5ik2o, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7bc2e5d4-1577-44fd-bbb2-223443c603de

📥 Commits

Reviewing files that changed from the base of the PR and between fb50897 and d3b7b80.

📒 Files selected for processing (5)
  • docs/reference/12-state-machine.ja.md
  • docs/reference/12-state-machine.md
  • packages/framework/core/knowledge/amadeus-shared/audit-format.md
  • packages/framework/core/tools/amadeus-advisory-choice.ts
  • tests/integration/t470-advisory-store-recovery.integration.test.ts
📝 Walkthrough

Walkthrough

Changes

schema 2 の advisory store に provenance union を追加しました。schema 1 store は自動変換せず拒否します。recover-schema-1 は pending advisory を検証して保持し、receipt を破棄して schema 2 として保存します。API、CLI、統合テスト、coverage 設定を追加しました。

Advisory store recovery

Layer / File(s) Summary
Schema 2 契約と回復手順
docs/reference/12-state-machine.*, packages/framework/core/knowledge/amadeus-shared/audit-format.md
receipt の provenance union、schema 1 store の fail-closed 動作、回復時の pending 保持と receipt 破棄を文書化します。
回復 API
packages/framework/core/tools/amadeus-advisory-choice.ts
schema 1 store、pending、intent ownership を検証します。pending を保存し、receipt を空にして回復統計を返します。
CLI と coverage 配線
packages/framework/core/tools/amadeus-advisory-choice.ts, tests/.coverage-patch-allowlist.json, tests/integration/t-coverage-mechanism-ratchet.test.ts
recover-schema-1 の dispatch、JSON 出力、終了コード、coverage 分類を追加します。
統合検証
tests/integration/t470-advisory-store-recovery.integration.test.ts
成功、拒否、ファイル未変更、report 状態、CLI の終了コード、未知の subcommand を検証します。

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、schema 1 の advisory choice store に復旧用 verb を追加する主変更を正確かつ簡潔に示しています。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/reference/12-state-machine.md`:
- Around line 336-339: Update the command example fenced blocks to specify the
shell language by changing the opening fences to ```sh in
docs/reference/12-state-machine.md lines 336-339 and
docs/reference/12-state-machine.ja.md lines 295-298.

In `@packages/framework/core/tools/amadeus-advisory-choice.ts`:
- Around line 1479-1485: Update foreignReceiptIntentRuns in
packages/framework/core/tools/amadeus-advisory-choice.ts (1479-1485) to return
missing or invalid identity.intentRun values as rejection markers, ensuring
receipts-only recovery fails closed instead of deleting unverified receipts.
Update the recovery contract in
packages/framework/core/knowledge/amadeus-shared/audit-format.md (114-119),
docs/reference/12-state-machine.md (348-360), and
docs/reference/12-state-machine.ja.md (302-302) to state that stores with
unverifiable receipt ownership are rejected and not written. Extend
tests/integration/t470-advisory-store-recovery.integration.test.ts (185-206) to
verify a receipts-only store lacking identity.intentRun is rejected and its file
remains unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5b676006-824c-420c-b8db-ba3630074c07

📥 Commits

Reviewing files that changed from the base of the PR and between 65658e0 and 82d5235.

📒 Files selected for processing (7)
  • docs/reference/12-state-machine.ja.md
  • docs/reference/12-state-machine.md
  • packages/framework/core/knowledge/amadeus-shared/audit-format.md
  • packages/framework/core/tools/amadeus-advisory-choice.ts
  • tests/.coverage-patch-allowlist.json
  • tests/integration/t-coverage-mechanism-ratchet.test.ts
  • tests/integration/t470-advisory-store-recovery.integration.test.ts

Comment thread docs/reference/12-state-machine.md Outdated
Comment thread packages/framework/core/tools/amadeus-advisory-choice.ts
j5ik2o added 2 commits August 7, 2026 16:07
The patch gate found the two arms that guard the READ itself uncovered:
the catch around JSON.parse and the shape check that follows it. Both are
exercised by the spawned CLI, but bun --coverage does not attribute a child
process to the parent report, so nothing measured them — and an arm nothing
measures is an arm nothing protects.

Both are now driven through the exported seam in-process, with the reason
string asserted exactly, because the reason is the only thing that says which
refusal actually ran: an identical "store is unreadable" message exists in
readStore, and a test that merely asserts a refusal cannot tell the two apart.

Refs #2330
The ownership check skipped any receipt whose identity it could not read. That
looked conservative and was the opposite: on the receipts-only path there is no
pending row to name the owner, so an unreadable receipt was deleted with its
owner never established — a hole in the very fail-closed contract the check
exists to hold.

Silence is not evidence of belonging. Reading an owner now yields `undefined`
for "the receipt does not say", and on the receipts-only path a single such
receipt refuses the recovery instead of being passed over. The path with
pending rows is untouched: there the pending identity already settles the owner.

Also labels the recover-schema-1 fence as `sh` in both documentation faces.

Refs #2330
@j5ik2o

j5ik2o commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

レビュー2件へ対応しました(head d3b7b8093)。

Major(所有者未確認 receipt の fail-open): 指摘どおり実害でした — identity.intentRun を読めない receipt を「所属の否定なし」として無検証破棄していました。是正: pending 0 件の経路で所有者を読めない receipt が1件でもあれば advisory choice store ownership cannot be verified で loud 拒否(変更ゼロ)。TDD Red 実測(是正前は回復が成功して削除される実害を再現)→ Green、落ちる実証(ガード無効化注入で新テストのみ赤)込み。audit-format.md / 12-state-machine.md / .ja.md の仕様記述も「所有者を読めない receipt では回復を拒否し書き込まない」へ整合。

Minor(コードフェンス言語): 両面へ sh を付与。なお同節の既存 record 例(EN :293-296 / JA :276-279)は本 PR 以前から言語指定なしのため、本 PR のスコープ(自分の追加分)に限定して対応しています。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant