Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
e96c5dc
feat(integrations): own value-free mutation planning in a pure seam
lidge-jun Sep 19, 2026
483ac7b
refactor(integrations): share one observation between preview and mut…
lidge-jun Sep 19, 2026
6b91ce8
test(integrations): pin the plan projection and fingerprint contract
lidge-jun Sep 19, 2026
8a6f7b4
fix(integrations): bind install state and admission to the plan finge…
lidge-jun Sep 19, 2026
24a372d
fix(integrations): publish only declared managed paths, as templates
lidge-jun Sep 19, 2026
b6a6bdb
test(integrations): prove real builder paths canonicalize, not just t…
lidge-jun Sep 19, 2026
d392407
feat(integrations): assemble the value-free plan an operator confirms
lidge-jun Sep 19, 2026
f8f53ed
feat(integrations): add the read-only preview entrypoint
lidge-jun Sep 19, 2026
d987ee4
test(integrations): prove a preview leaves the filesystem untouched
lidge-jun Sep 19, 2026
5a6b090
fix(integrations): decide each operation the way that operation decides
lidge-jun Sep 19, 2026
552ea80
feat(management): let a read-only caller bring its own model roster
lidge-jun Sep 19, 2026
5c0dd83
feat(management): serve integration mutation plans on read-only routes
lidge-jun Sep 19, 2026
72e906d
test(management): cover the preview routes against the real temp home
lidge-jun Sep 19, 2026
5bc8393
fix(integrations): plan an undo the way the writer performs one
lidge-jun Sep 19, 2026
6a9974a
fix(management): never gather a model roster to serve a preview
lidge-jun Sep 19, 2026
f4c090e
feat(management): bind a confirmed plan to the mutation it authorizes
lidge-jun Sep 19, 2026
23d36cd
fix(integrations): check a confirmation where the lock already holds
lidge-jun Sep 19, 2026
2fa1634
fix(integrations): check an Aside confirmation before its preference …
lidge-jun Sep 19, 2026
007d14a
test(management): a bound change cannot commit on a plan that fails t…
lidge-jun Sep 19, 2026
e2f2a78
fix(integrations): answer an expired backup and a vanished file the w…
lidge-jun Sep 19, 2026
1aad46b
fix(management): preview reads a finished roster, not raw provider ca…
lidge-jun Sep 19, 2026
3e70b2d
fix(integrations): actually check a bound undo, and bind it to one ro…
lidge-jun Sep 19, 2026
839da3e
fix(management): never silently drop a preview binding on the Aside r…
lidge-jun Sep 19, 2026
1f6bcb4
test(management): prove a confirmation is compared, not merely required
lidge-jun Sep 19, 2026
d8de0d1
fix(management): carry the binding into every path that can mutate
lidge-jun Sep 19, 2026
43756d6
fix(management): forward the binding through the nested Aside restore…
lidge-jun Sep 19, 2026
8c3e4a2
fix(management): validate the roster the mutation will actually write
lidge-jun Sep 19, 2026
579f387
feat(integrations): plan one Aside profile's change without performin…
lidge-jun Sep 19, 2026
b8ff896
feat(management): honour a confirmed plan on Aside profile changes
lidge-jun Sep 19, 2026
edf2c75
fix(management): take the roster and its identity in one read
lidge-jun Sep 19, 2026
5b30922
fix(management): keep the roster snapshot independent of its producer
lidge-jun Sep 19, 2026
e0a89d1
test(management): a refused confirmation writes nothing anywhere
lidge-jun Sep 19, 2026
2e37a53
fix(management): clone the roster on the way out too, and check write…
lidge-jun Sep 19, 2026
45b2f9f
test(management): pin the interleaving the identity check exists for
lidge-jun Sep 19, 2026
d891209
test(management): carry a bound undo all the way through
lidge-jun Sep 19, 2026
94654a9
feat(management): route the Aside profile preview
lidge-jun Sep 19, 2026
50ecfda
docs(structure): record who owns a mutation plan and why it is placed…
lidge-jun Sep 19, 2026
e53d68d
fix(management): retire the roster snapshot when discovery moves unde…
lidge-jun Sep 19, 2026
1c37027
test(management): assert what a successful undo actually does
lidge-jun Sep 19, 2026
e99250d
fix(models): retire a derived roster on publication, not on authority…
lidge-jun Sep 19, 2026
c4d207c
fix(integrations): plan the undo row the mutation will actually use
lidge-jun Sep 19, 2026
7117736
fix: unblock the typecheck and finish carrying one selected row and o…
lidge-jun Sep 19, 2026
55ba4d5
fix(models): prune revision entries, and advance the global term befo…
lidge-jun Sep 19, 2026
ba3311b
test(management): prove a bound profile change is planned, committed …
lidge-jun Sep 19, 2026
5815b4f
fix(models): drop a pruned provider's revision after the cache deleti…
lidge-jun Sep 19, 2026
0db8dd0
test(models): make the prune case actually remove a cached provider
lidge-jun Sep 19, 2026
c892334
test(management): witness the whole store when a profile confirmation…
lidge-jun Sep 19, 2026
69fd770
fix(management): identify a roster by the configuration that was admi…
lidge-jun Sep 19, 2026
43e1520
fix(management): a missing config file is defaults, not an unprovable…
lidge-jun Sep 19, 2026
84464a7
test(management): pin both halves of the configuration identity
lidge-jun Sep 19, 2026
1228ac2
docs(management-api): document previewing and confirming an integrati…
lidge-jun Sep 19, 2026
1734e37
docs(ko): document previewing and confirming an integration change
lidge-jun Sep 19, 2026
d944bf3
docs(ja, fr): document previewing and confirming an integration change
lidge-jun Sep 19, 2026
ea8b89f
docs(ru, tr, zh-cn, zh-tw): document previewing and confirming an int…
lidge-jun Sep 19, 2026
2738c71
docs: correct what an unavailable roster actually means
lidge-jun Sep 19, 2026
3448840
test(management): a duplicate copy of an operation cannot redirect a …
lidge-jun Sep 19, 2026
36b7ddd
test(management): make the duplicate copy one resolution actually has…
lidge-jun Sep 19, 2026
11551f2
fix(models): carry each provider's revision out with the rows it vouc…
lidge-jun Sep 19, 2026
8663aac
test(integrations): make the captured-row handoff observable, not mer…
lidge-jun Sep 19, 2026
76864f7
test(integrations): choose the captured copy when only lookup diverges
lidge-jun Sep 19, 2026
65f5d57
test(models): interleave two publications inside one export load
lidge-jun Sep 19, 2026
52d084a
fix(management): identify a roster by the configuration in hand, not …
lidge-jun Sep 19, 2026
9848c50
fix(management): gather and project one admitted configuration, then …
lidge-jun Sep 19, 2026
a2dc604
Merge remote-tracking branch 'origin/dev' into codex/5118-integration…
lidge-jun Sep 19, 2026
7ecd76f
fix(integrations): freeze the configuration a coordinated write was c…
lidge-jun Sep 19, 2026
87e8a63
fix(integrations): one input from the check to the document, or a ref…
lidge-jun Sep 19, 2026
78ffab1
test(integrations): pin that a committed Aside change leaves the rost…
lidge-jun Sep 19, 2026
2ef5b41
test(integrations): say what a committed Aside change does to the roster
lidge-jun Sep 19, 2026
be6751b
fix(integrations): check the Aside input that will be written, prepar…
lidge-jun Sep 19, 2026
edbc4dc
Merge remote-tracking branch 'origin/dev' into codex/5118-integration…
lidge-jun Sep 19, 2026
36cdb56
fix(integrations): bring the Aside input forward only for a checked c…
lidge-jun Sep 19, 2026
d7590af
fix(config): treat a non-executor provider fetch field as data, not a…
lidge-jun Sep 19, 2026
ea8c69c
test(management): cover both halves of the provider fetch admission
lidge-jun Sep 19, 2026
104b5a4
fix: answer the review on the written fetch value, the roster identit…
lidge-jun Sep 19, 2026
a1eeb80
fix(integrations): repair the typecheck and answer both review findings
lidge-jun Sep 19, 2026
f4db3ff
test(management): separate a replaced roster from one rebuilt unchanged
lidge-jun Sep 19, 2026
80a1c4f
fix(integrations): reach the undo classification from a real preview,…
lidge-jun Sep 19, 2026
ea4b55f
fix(integrations): read an undo's current ownership from the file it …
lidge-jun Sep 19, 2026
3a8e73d
Merge remote-tracking branch 'origin/dev' into codex/5118-integration…
lidge-jun Sep 19, 2026
fe77823
fix(integrations): refuse an unscoped Aside plan at the generic previ…
lidge-jun Sep 19, 2026
0c67127
docs(management-api): name the unavailable-roster refusal on the prof…
lidge-jun Sep 19, 2026
9549ac8
docs(management-api): scope the no-op statement to the client document
lidge-jun Sep 19, 2026
c270fb8
fix(integrations): check the target once more before an Aside write
lidge-jun Sep 19, 2026
5bd1d31
fix(integrations): refuse an Aside restore before its history is copied
lidge-jun Sep 19, 2026
c67cf0b
fix(integrations): keep both looks at the target around an Aside restore
lidge-jun Sep 19, 2026
e7bc9e9
test(integrations): witness the history copy before a competing edit
lidge-jun Sep 20, 2026
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
47 changes: 47 additions & 0 deletions docs-site/src/content/docs/fr/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,53 @@ Pour comprendre la liste de modèles et le comportement chiffré des tâches con
| `GET /api/client-integrations/journal?client=...` | Lister les opérations de restauration, éventuellement pour un seul client. Chaque ligne contient le champ `deletable` calculé par le serveur. | 400 client invalide |
| `DELETE /api/client-integrations/journal?opId=...` | Retirer une ancienne opération et supprimer son instantané si possible. La réponse contient `snapshotRemoved` ; `false` conserve le nettoyage pour une nouvelle tentative de maintenance. | 400 `opId` absent ; 404 opération absente ou déjà retirée ; 409 opération la plus récente du client |

## Prévisualiser une modification d'intégration

Une prévisualisation montre ce qu'une modification ferait sans la faire. Ces routes n'écrivent
rien : ni instantané, ni enregistrement de propriété, ni ligne de journal, ni verrou, ni
maintenance, ni récupération.

| Méthode et chemin | Objet | Erreurs notables |
| --- | --- | --- |
| `POST /api/client-integrations/preview` | Planifier `apply`, `overwrite` ou `disable` pour un client ; corps `{ "clientId": "...", "operation": "..." }` | 400 client ou opération invalide ; 400 `invalid_aside_profile_path`; 409 `integration_preview_unavailable` |
| `POST /api/client-integrations/restore/preview` | Planifier une annulation ; corps `{ "opId": "...", "confirmDrift": false }` | 404 opération inconnue ; 400 `invalid_aside_profile_path`; 409 `integration_preview_unavailable` |
| `POST /api/client-integrations/aside/profiles/{profileId}/preview` | Planifier la modification d'un seul profil Aside ; `restore` exige un `opId` | 400 corps invalide ou profil non précisé ; 404 profil ou opération inconnus; 409 `integration_preview_unavailable` |

Un plan contient `version`, `clientId`, `operation`, `state`, `foreignEdit`, une liste `changes`
de paires `kind` et `path`, une empreinte `fingerprint` opaque, `canApply`, `willChange`, ainsi
que `refusalReason` et `profileId` facultatifs. Les chemins sont des chemins de schéma gérés ou
les marqueurs fixes `$snapshot`, `$ownership` et `$journal` ; une position déterminée à
l'exécution apparaît comme `*`. Aucune valeur de configuration, aucun emplacement de fichier et
aucune identité d'élément sélectionné n'est renvoyé.

`canApply` à vrai avec `willChange` à faux signifie que l'opération réussit sans rien changer
dans le document client géré, par exemple appliquer ce qui est déjà appliqué.

Une modification de profil Aside enregistre tout de même une chose dans ce cas : la confirmation
enregistre la préférence de synchronisation du profil avant de toucher au moindre document client.
Désactiver un profil dont le bloc géré est déjà absent enregistre donc la préférence et laisse le
document et son historique intacts.

`integration_preview_unavailable` indique qu'aucune liste de modèles utilisable n'est actuellement
conservée : un proxy qui vient de démarrer est un cas, une liste abandonnée parce que la
configuration ou le cache de fournisseurs a changé en est un autre. Lire
`GET /api/client-integrations` en établit une lorsque la découverte réussit et que la
configuration peut être identifiée ; c'est le remède habituel, pas une garantie.

## Confirmer une modification prévisualisée

Les routes de modification acceptent `operation` et `planFingerprint` à côté de leur corps
habituel. Envoyez les deux ou aucun : une requête n'en portant qu'un est rejetée, tout comme une
requête dont l'`operation` contredit la modification demandée. Une liaison Aside porte sur un seul
profil, car une empreinte ne peut pas décrire plusieurs fichiers qui changent indépendamment.

Le serveur replanifie avant d'écrire et renvoie `409 integration_preview_stale` avec un `plan`
recalculé lorsque la confirmation ne décrit plus ce qui se produirait. Décidez de nouveau d'après
ce plan ; la requête n'est pas réessayée automatiquement.

Une empreinte est une vérification optimiste, jamais une autorisation. L'authentification de l'API
d'administration et les règles de propriété décident seules si une modification peut avoir lieu.

La suppression ajoute une pierre tombale au lieu de réécrire le journal. Le serveur protège
l'opération la plus récente de chaque client afin de conserver le point d'annulation actuel.

Expand Down
43 changes: 43 additions & 0 deletions docs-site/src/content/docs/ja/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,49 @@ Authorization: Bearer <admin-token>
| `GET /api/client-integrations/journal?client=...` | ロールバック操作を一覧表示します。任意でクライアントを指定でき、各行にはサーバー計算の `deletable` が含まれます。 | 400 無効なクライアント |
| `DELETE /api/client-integrations/journal?opId=...` | 古いロールバック操作を廃止し、可能ならスナップショットも削除します。成功時の `snapshotRemoved` が `false` の場合、保守処理で再試行されます。 | 400 `opId` なし、404 存在しないか廃止済み、409 そのクライアントの最新操作 |

## 統合変更のプレビュー

プレビューは変更を適用せずに内容だけを示します。スナップショットも所有権記録もジャーナルも
ロックも復旧も、いっさい書き込みません。

| メソッドとパス | 目的 | 主なエラー |
| --- | --- | --- |
| `POST /api/client-integrations/preview` | クライアント 1 つの `apply`、`overwrite`、`disable` を計画します。本文は `{ "clientId": "...", "operation": "..." }` | 400 不正なクライアントまたは操作、400 `invalid_aside_profile_path`、409 `integration_preview_unavailable` |
| `POST /api/client-integrations/restore/preview` | 取り消しを計画します。本文は `{ "opId": "...", "confirmDrift": false }` | 404 該当操作なし、400 `invalid_aside_profile_path`、409 `integration_preview_unavailable` |
| `POST /api/client-integrations/aside/profiles/{profileId}/preview` | Aside プロファイル 1 つの変更を計画します。`restore` には `opId` が必要です | 400 不正な本文またはプロファイル未指定、404 該当プロファイルまたは操作なし、409 `integration_preview_unavailable` |

計画には `version`、`clientId`、`operation`、`state`、`foreignEdit`、`kind` と `path` の組からなる
`changes`、不透明な `fingerprint`、`canApply`、`willChange` が含まれ、`refusalReason` と
`profileId` は任意です。パスは管理対象スキーマのパスか、`$snapshot`、`$ownership`、`$journal`
の固定表記で、実行時に決まる位置は `*` になります。設定値やファイルの場所、選ばれた項目の
名前は返しません。

`canApply` が真で `willChange` が偽なら、操作は成功しますが管理対象のクライアント文書は何も
変わりません。すでに適用済みのものを適用した場合などです。

Aside プロファイルの変更はこの場合でも一つだけ保存します。確認を送ると、クライアント文書に触れる
前にそのプロファイルの同期設定が記録されるため、管理ブロックがすでにないプロファイルを無効にすると
設定だけが保存され、文書とその履歴はそのまま残ります。

`integration_preview_unavailable` は今使えるモデル一覧がないという意味です。プロキシを起動した
直後もそうですし、設定やプロバイダーキャッシュが変わって以前の一覧を破棄した場合もそうです。
`GET /api/client-integrations` を読むと、取得に成功し設定を特定できたときに用意されるので、
通常はこれで解決しますが、必ず用意されるとはかぎりません。

## プレビューした変更の確定

変更要求の本文に `operation` と `planFingerprint` を一緒に送ります。両方送るか両方省くかの
どちらかで、片方だけ、または要求と異なる操作を書いた場合は拒否します。Aside はプロファイル
1 つにしか結び付けられません。1 つのフィンガープリントで複数ファイルの変更は説明できないから
です。

サーバーは書き込む前に計画し直し、確認した内容がもう当てはまらなければ新しい計画を添えて
`409 integration_preview_stale` を返します。自動で再試行はしないので、新しい計画を見て判断し
直してください。

フィンガープリントは楽観的な確認であって権限ではありません。変更してよいかどうかは管理 API の
認証と所有権の規則が決めます。

削除時はジャーナルを書き換えず、トゥームストーンを追記します。現在の取り消し地点を
残すため、各クライアントの最新操作はサーバー側で保護されます。

Expand Down
42 changes: 42 additions & 0 deletions docs-site/src/content/docs/ko/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,48 @@ Authorization: Bearer <admin-token>
| `GET /api/client-integrations/journal?client=...` | 롤백 작업을 조회합니다. 특정 클라이언트로 제한할 수 있으며 각 행에는 서버가 계산한 `deletable` 값이 포함됩니다. | 400 잘못된 클라이언트 |
| `DELETE /api/client-integrations/journal?opId=...` | 이전 롤백 작업을 폐기하고 가능한 경우 스냅샷도 제거합니다. 성공 응답의 `snapshotRemoved`가 `false`면 유지보수 재시도를 위해 정리 작업이 보존됩니다. | 400 `opId` 누락, 404 없거나 이미 폐기된 작업, 409 해당 클라이언트의 최신 작업 |

## 통합 변경 미리 보기

미리 보기는 변경 내용을 적용하지 않고 보여 줍니다. 스냅샷도, 소유권 기록도, 저널도, 잠금도,
복구도 남기지 않습니다.

| 메서드 및 경로 | 목적 | 주요 오류 |
| --- | --- | --- |
| `POST /api/client-integrations/preview` | 클라이언트 하나의 `apply`, `overwrite`, `disable`을 계획합니다. 본문은 `{ "clientId": "...", "operation": "..." }` | 400 잘못된 클라이언트나 작업, 400 `invalid_aside_profile_path`, 409 `integration_preview_unavailable` |
| `POST /api/client-integrations/restore/preview` | 실행 취소를 계획합니다. 본문은 `{ "opId": "...", "confirmDrift": false }` | 404 없는 작업, 400 `invalid_aside_profile_path`, 409 `integration_preview_unavailable` |
| `POST /api/client-integrations/aside/profiles/{profileId}/preview` | Aside 프로필 하나의 변경을 계획합니다. `restore`에는 `opId`가 필요합니다 | 400 잘못된 본문이나 프로필 미지정, 404 없는 프로필이나 작업, 409 `integration_preview_unavailable` |

계획에는 `version`, `clientId`, `operation`, `state`, `foreignEdit`, `kind`와 `path` 쌍으로 이루어진
`changes`, 불투명한 `fingerprint`, `canApply`, `willChange`가 담기고 `refusalReason`과
`profileId`는 선택 항목입니다. 경로는 관리 대상 스키마 경로이거나 `$snapshot`, `$ownership`,
`$journal` 표식이며, 실행 중에 정해지는 자리는 `*`로 적습니다. 설정 값이나 파일 위치, 선택된
항목의 이름은 돌려주지 않습니다.

`canApply`가 참인데 `willChange`가 거짓이면 작업은 성공하지만 관리 대상 클라이언트 문서에서는
아무것도 바뀌지 않습니다. 이미 적용된 것을 다시 적용하는 경우가 그렇습니다.

Aside 프로필 변경은 이때도 한 가지를 저장합니다. 확인을 보내면 클라이언트 문서에 손대기 전에 그
프로필의 동기화 설정이 먼저 기록되므로, 관리 블록이 이미 없는 프로필을 끄면 설정만 저장되고
문서와 기록은 그대로 남습니다.

`integration_preview_unavailable`은 지금 쓸 수 있는 모델 목록이 없다는 뜻입니다. 프록시를 막
시작했을 때도 그렇고, 설정이나 공급자 캐시가 바뀌어 기존 목록을 버린 경우에도 그렇습니다.
`GET /api/client-integrations`를 읽으면 조회가 성공하고 설정을 확인할 수 있을 때 목록이
준비되므로, 보통은 이렇게 해결되지만 항상 보장되지는 않습니다.

## 미리 본 변경 확정하기

변경 요청 본문에 `operation`과 `planFingerprint`를 함께 보냅니다. 둘 다 보내거나 둘 다
생략해야 하며, 하나만 보내거나 요청과 다른 작업을 적으면 거부합니다. Aside는 프로필 하나에만
묶을 수 있습니다. 지문 하나로 여러 파일의 변경을 설명할 수는 없기 때문입니다.

서버는 쓰기 전에 다시 계획해 보고, 확인한 내용이 더 이상 맞지 않으면 새 계획을 담아
`409 integration_preview_stale`을 돌려줍니다. 자동으로 다시 시도하지 않으니 새 계획을 보고
다시 결정하면 됩니다.

지문은 낙관적 확인일 뿐 권한이 아닙니다. 변경을 허용할지는 관리 인증과 소유권 규칙이
결정합니다.

삭제는 저널을 다시 쓰지 않고 툼스톤을 추가합니다. 현재 실행 취소 지점을 유지하기 위해
각 클라이언트의 최신 작업은 서버에서 삭제하지 못하게 보호합니다.

Expand Down
45 changes: 45 additions & 0 deletions docs-site/src/content/docs/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,51 @@ nonnegative integer account ID returned by the profile list; it is not a browser
| `POST /api/client-integrations/aside/profiles/{profileId}/restore` | Undo an operation using `{ "opId": "..." }`; optional `confirmDrift: true` permits replacing later edits | 404 missing operation/profile; 409 busy, mismatch, or required drift confirmation; 410 expired snapshot; 500 save/write failure |
| `POST /api/client-integrations/aside/sync` | Refresh enabled profiles through the server's mutation owner; body `{}` | 400 nonempty body/profile selector; 409 busy; 207 per-profile refusals |

## Previewing an integration change

A preview reports what a change would do without doing it. These routes write nothing: no
snapshot, no ownership record, no journal row, no lock, and no maintenance or recovery.

| Route | Purpose | Notable responses |
| --- | --- | --- |
| `POST /api/client-integrations/preview` | Plan `apply`, `overwrite` or `disable` for one client; body `{ "clientId": "...", "operation": "..." }` | 400 invalid client or operation; 400 `invalid_aside_profile_path`; 409 `integration_preview_unavailable` |
| `POST /api/client-integrations/restore/preview` | Plan an undo; body `{ "opId": "...", "confirmDrift": false }` | 404 unknown operation; 400 `invalid_aside_profile_path`; 409 `integration_preview_unavailable` |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `POST /api/client-integrations/aside/profiles/{profileId}/preview` | Plan one Aside profile's change, including `restore` with an `opId` | 400 invalid body or unscoped request; 404 unknown profile or operation, 409 `integration_preview_unavailable` |

A plan carries `version`, `clientId`, `operation`, `state`, `foreignEdit`, a `changes` list of
`kind` and `path` pairs, an opaque `fingerprint`, `canApply`, `willChange`, and an optional
`refusalReason` and `profileId`. Paths are managed schema paths or the fixed `$snapshot`,
`$ownership` and `$journal` markers; a position chosen at runtime appears as `*`. No
configuration value, file location or selected member identity is returned.

`canApply: true` with `willChange: false` means the operation succeeds and changes nothing in
the managed client document, such as applying what is already applied.

An Aside profile change still saves one thing in that case. Confirming it records the desired sync
preference for that profile before any client document is touched, so disabling a profile whose
managed block is already absent stores the preference and leaves the document and its history
untouched.

`integration_preview_unavailable` means no usable model roster is currently retained. A freshly
started proxy is one way to be in that state; a roster retired because the configuration or the
provider cache moved is another. Reading `GET /api/client-integrations` establishes one when
discovery succeeds and the configuration can be identified, so it is the usual remedy rather than
a guarantee.

## Confirming a previewed change

Mutation routes accept `operation` and `planFingerprint` alongside their existing body. Send both
or neither: a request carrying one is rejected, as is one whose `operation` disagrees with the
change being requested. Aside bindings apply to a single profile, because one fingerprint cannot
describe several independently changing files.

The server re-plans before writing anything and returns `409 integration_preview_stale` with a
freshly computed `plan` when the confirmation no longer describes what would happen. Decide again
against the new plan; the request is not retried automatically.

A fingerprint is an optimistic check, never authorization. Management authentication and every
ownership rule still decide whether a change may happen at all.

Bulk PUT returns `{ ok, clientId, changed, state, message, results }`; each result identifies
its `profileId` and reports the writer outcome. Sync returns `{ ok, clientId, results }`, with
per-profile refresh outcomes. Both return HTTP 200 when all returned attempts succeed and
Expand Down
Loading
Loading