promote: graph-path promotion as a server verb (MCP promote tool, ADR 0018) - #517
Conversation
POST /contexts/{name}/promote and the MCP promote tool (#466 S2): the
named scratch sources move into an established destination context as
export -> filter -> re-head -> import -> audit, one request, no LLM
anywhere in the path. Each source moves whole (passage, date, tags,
only its own share of every edge; aliases exactly when their canonical
is live in the promoted slice, the rest counted), source ids survive,
and per-source retract-then-apply makes re-promotion idempotent. The
destination is never created, its own schema judges the incoming
batches, and the landing-zone consolidation audit rides back as
candidates. Write role, retract_source's classification; the body's
destination is scope-checked like /import's body contexts.
Building the dry run exposed two standing /import?dry_run defects the
same change fixes at the root (PreviewSeeds): a preview held no
cross-batch state, so an export-shaped stream whose aliases trail
their canonicals, and every post-first batch of a fresh-name restore,
refused spuriously where the real import applies cleanly.
Claude-Session: https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
Without the guard, a literal export:unsourced attribution (a prior import round trip's residue) would slip through the source filter as sourceless weight — sourceless weight deliberately cannot travel with a promotion. Claude-Session: https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
Scoped-key gate, retracted-source unavailability, refusal detail fields, warn-mode envelope accounting, quota pre-check firing shape, and the mean-not-product weight recomputation each get a pinning test. The two timing-only refusal arms (budget spent mid-loop, render dying mid-request) move into #[mutants::skip] helpers with the reason in place; the quota refusal helper documents why its durable>0 shape cannot be pinned (single destination, every batch carries growth). Claude-Session: https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
The warn-issue cap loses its boundary comparison (unconditional extend-then-truncate — each batch's issues are already capped, so the overshoot is bounded); audit_skip_reason is skip-annotated (every arm needs a post-apply race no test can time); strip_create's contract gets a direct NoContext pin; and the preview's had_passage guard gets a phantom-drop assertion on the dry-run shape. Claude-Session: https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
The count-0 attribution rows the >-vs->= mutant needs cannot be staged over HTTP (retraction removes the rows outright), so the existence check moves next to filter_to_sources as a pure function and the unit test constructs the residue directly. Claude-Session: https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthrough構造化された scratch source を既存コンテキストへ昇格する HTTP API と MCP ツールを追加しました。ソースフィルタ、dry-run、冪等適用、監査、認可、スキーマ検証を実装しました。import dry-run はバッチ間状態を継承します。 Changesグラフパス昇格
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant MCPRoute
participant PromoteAPI
participant ExportSnapshot
participant ImportAPI
Caller->>MCPRoute: promote(context, into, sources, dry_run)
MCPRoute->>PromoteAPI: POST /contexts/{name}/promote
PromoteAPI->>ExportSnapshot: filter_to_sources(sources)
ExportSnapshot-->>PromoteAPI: filtered snapshot
PromoteAPI->>ImportAPI: preview or apply batches
ImportAPI-->>PromoteAPI: batch outcomes
PromoteAPI-->>Caller: PromoteOutcome
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 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/promotion.html`:
- Around line 119-126: Update docs/promotion.html lines 119-126 to document the
HTTP contract as POST /contexts/{scratch}/promote?dry_run=true with body {into,
sources, audit?}, and state that dry runs preview transfer batches without
executing or returning audit results. Update
examples/skills/memory-promotion/SKILL.md lines 55-58 to state that dry_run:
true returns only the batches and that step 5 audit decisions require a real
apply.
In `@examples/skills/memory-promotion/SKILL.md`:
- Around line 51-53: Update the promote MCP tool example in the surrounding
memory-promotion instructions to include the required context argument alongside
into and sources, using the scratch context referenced by the text; preserve the
existing source session IDs and promotion target.
In `@src/api/promote.rs`:
- Around line 364-371: Update the promote route around landing_audit so its
heavy graph audit is covered by HeavyOpsLimiter, matching the existing
audit_drift pattern: obtain the limiter through the route’s Extension and
acquire a permit only in the branch that executes landing_audit. Preserve the
current dry-run and request.audit bypass behavior, and ensure the permit remains
held for the complete landing_audit call.
- Around line 372-382: promote のHTTP wire fixtureを追加し、POST
/contexts/{name}/promote の代表レスポンスとして batches、aliases_dropped、audit、audit_skipped
を含めてください。ok_with_issues_total に渡される PromoteOutcome の形と整合させ、HTTP_CONTRACT と
MCP_CONTRACT は変更しないでください。
In `@src/ingest/rejection.rs`:
- Around line 279-286: Update PreviewSeeds::absorb to store interned concepts
and labels grouped by context, and change predicted_alias_rejection to pass only
the seeds matching batch.context. Preserve rejection for aliases whose canonical
vocabulary is absent in the current context, and add a regression test covering
interleaved batches from different contexts.
In `@src/llm-protocol.md`:
- Line 365: Add an HTTP wire golden fixture for POST /contexts/{name}/promote
under the existing HTTP fixture set, covering batches, aliases_dropped, audit,
audit_skipped, and the omission rules for dry_run responses. Update
HTTP_CONTRACT with the fixture/version entry as needed, while leaving
MCP_CONTRACT version-only and without duplicating the response fixture because
MCP uses HTTP pass-through behavior.
In `@src/main.rs`:
- Line 904: Update promote_sources and the /contexts/{name}/promote route flow
to obtain the shared HeavyOpsLimiter permit only when audit=true and
dry_run=false, holding it for the full landing_audit execution. Do not acquire a
permit for audit=false or dry_run=true, and reuse the existing limiter rather
than creating a separate one.
In `@src/mcp/schema.rs`:
- Around line 581-583: Update the MCP schema’s sources property in the promote
tool definition to require at least one item by adding the appropriate minItems
constraint, matching promote_sources validation. Add a schema validation test
that confirms an empty sources array is rejected.
🪄 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: CHILL
Plan: Pro Plus
Run ID: 36a9d86d-088b-48e6-994e-5982266be9e8
📒 Files selected for processing (22)
CHANGELOG.mdadr/0018-graph-path-promotion.mddocs/promotion.htmlexamples/skills/memory-promotion/SKILL.mdsrc/api.rssrc/api/consolidation.rssrc/api/import.rssrc/api/promote.rssrc/auth.rssrc/export.rssrc/ingest.rssrc/ingest/model.rssrc/ingest/rejection.rssrc/ingest/tests.rssrc/llm-protocol.mdsrc/main.rssrc/mcp.rssrc/mcp/route.rssrc/mcp/schema.rssrc/route.rstests/http_api/main.rstests/http_api/promote.rs
…, contract fixtures)
- PreviewSeeds now keys its interned vocabulary by context: a stream
can interleave contexts, and a sibling's names must not vouch for an
alias here — the real import refuses UnknownCanonical, so the
preview does too (regression: interleaved-context stream refuses
identically on dry run and real).
- landing_audit spends a heavy-ops permit (audit_drift's conditional
pattern) — the write half never gates, and at the ceiling the audit
degrades to audit_skipped: "overloaded" instead of shedding a
request whose batches are already durable.
- The MCP schema declares minItems: 1 on sources, matching the
handler's refusal; wire fixtures pin the promote and dry-run
response shapes; docs and the skill template state the HTTP contract
(?dry_run=true, {into, sources, audit?}, dry runs never audit) and
the tool call's required context argument.
Claude-Session: https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
#466 S2(再評価コメントの分割 2/3): graph 経路の昇格を server verb として束ねる — ADR 0018。
何をするか
POST /contexts/{name}/promote+ MCP toolpromote: scratch の source id 列を、確立済みの恒久 context へ export → filter → re-head → import → audit の 1 リクエストで昇格する。経路上に LLM なし・新 credential 面なし(ADR 0009 §13 の境界は不変)。into宛に render →/importと同じ retract-then-apply で適用。再昇格は冪等。alias は canonical が昇格 slice に live なものだけ運ばれ、残りはaliases_droppedで計数。stored_at・date・tags が原文どおり運ばれ、昇格後も citation はセッションを指す。intoは既存必須(create block は strip — 競合削除時は NoContext 拒否)。scratch の退役は明示のretract_sourceのまま(step 5/6)。nothing_written)。予約 id(export:unsourced/export:empty)も拒否。intoの consolidation audit(3 check・既定上限)がauditで返る — candidates のみ。audit: falseで opt-out、失敗時はaudit_skipped(書き込みは既に durable のため degrade)。?dry_run=trueは同形 preview・無書込・無 audit。retract_sourceと同分類 — context を作れず group/schema を運ばない)。intoは/importの body-context 同様に handler で scope 判定。router 経由は scratch の shard へ丸ごと proxy(両 context 同一 shard 要件 — route.rs の divergence リストに追記)。併せて直した既存バグ(dry-run 構築中に発覚)
POST /import?dry_run=trueは cross-batch 状態を持たず、実 import が通るストリームを 2 パターンで誤拒否していた:UnknownCanonicalno_contextPreviewSeedsで「先行バッチが intern/create するはずのもの」を各バッチの検査に種付けして解消。cross-batch alias conflict は引き続き非予測(preview が実行より甘くなる方向 = 容量チェックと同じ advisory 側)。テスト
filter_to_sources(指名 share のみ・残差ゼロ・schema 非搬送)、MCP routing(dry_run=query/引数拒否)、auth role 表tests/http_api/promote.rs): 移動と冪等性(share 分離・provenance・alias 会計・audit 同梱)、dry-run 無書込、拒否系(空/自己/宛先なし/未知 id/予約 id)、宛先 strict schema の判定+scratch schema 非感染、export ストリームの dry-run round trip(PreviewSeeds)、MCP tool e2eMutation gate
diff-scoped で予算(60)超過(初回 78 件)。over-budget の規定は module sweep だが、本 diff は新規モジュール(
api/promote.rs)+既存 5 ファイルへの小追加で、既存ファイルの sweep は無関係な既存 mutant を数百件回すことになるため、S1(PR #513)の前例に従い全件をローカルで diff-scoped 実行し、missed ゼロまで解消した:strip_create契約、preview の phantompassage_dropped)+タイミング競合でのみ到達可能な 4 アームを helper 抽出#[mutants::skip](理由コメント付き: budget/render の途中失効、quota の durable>0 形、audit 失敗理由マップ)+比較面の除去 2 箇所(warn-issue cap の無条件 extend+truncate、available_sourcesの純関数抽出 — count-0 attribution 残渣は HTTP 経由で構成不能のため unit で直接 pin)分割の位置づけ
promote(graph 経路)https://claude.ai/code/session_01UPjzCLVuXQmxhGkr6phPb9
Summary by CodeRabbit
新機能
バグ修正
ドキュメント