fix(backend): clone du seed OpenCode tolère un seed persisté converti en cloud
CloneOpenCodeProfileFromSeed::execute rejetait auparavant tout profil persisté squattant l'UUID déterministe du seed canonique opencode-llamacpp avec « internal error: canonical OpenCode seed is not an OpenCode profile ». Root cause: un profil persisté converti en backend cloud GLM5.2 (ticket #92, opencodeProvider) conserve l'UUID du seed ; le find le matchait et le garde-fou seed.opencode.is_none() — non mis à jour pour le backend cloud — déclenchait une erreur Internal bloquante. Correctif: - Le find exige désormais un seed OpenCode valide (structured_adapter OpenCode + au moins un backend opencode local OU opencode_provider cloud), et retombe sur le seed catalogue sinon au lieu d'errer sur un état de store inattendu. - L'override opencode local remet opencode_provider = None pour honorer l'invariant #97 opencode_backend_is_consistent (miroir de AgentProfile::with_opencode). - 2 tests de régression (fallback catalogue, seed cloud accepté) + 1 test de symétrie save ajoutés. Vérification: cargo test -p application --test profile_usecases -> 25 passed.
This commit is contained in:
@ -196,9 +196,27 @@ impl CloneOpenCodeProfileFromSeed {
|
||||
) -> Result<CloneOpenCodeProfileFromSeedOutput, AppError> {
|
||||
let existing = self.store.list().await?;
|
||||
let seed_id = reference_profile_id("opencode-llamacpp");
|
||||
// A valid OpenCode seed carries the OpenCode structured adapter and at
|
||||
// least one backend: local (`opencode`, llamacpp) OR cloud
|
||||
// (`opencode_provider`, ticket #92). The `opencode_backend_is_consistent`
|
||||
// invariant (#97) guarantees at most one of the two is set; a profile
|
||||
// carrying neither is not a usable seed.
|
||||
//
|
||||
// A persisted profile with the canonical seed id is preferred when it is
|
||||
// still a valid OpenCode seed, so local edits — including a cloud
|
||||
// conversion that reused the deterministic seed id — are preserved as the
|
||||
// clone template. When the persisted slot is NOT a valid OpenCode seed
|
||||
// (an unrelated profile squatting the id, or an OpenCode profile missing
|
||||
// both backends), fall back to the in-memory reference catalogue seed,
|
||||
// which is always the valid local llamacpp — instead of erroring on an
|
||||
// unexpected store state.
|
||||
let seed = existing
|
||||
.iter()
|
||||
.find(|profile| profile.id == seed_id)
|
||||
.find(|profile| {
|
||||
profile.id == seed_id
|
||||
&& profile.structured_adapter == Some(StructuredAdapter::OpenCode)
|
||||
&& (profile.opencode.is_some() || profile.opencode_provider.is_some())
|
||||
})
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
reference_profiles()
|
||||
@ -209,12 +227,6 @@ impl CloneOpenCodeProfileFromSeed {
|
||||
AppError::Internal("canonical OpenCode seed `opencode-llamacpp` is missing".into())
|
||||
})?;
|
||||
|
||||
if seed.structured_adapter != Some(StructuredAdapter::OpenCode) || seed.opencode.is_none() {
|
||||
return Err(AppError::Internal(
|
||||
"canonical OpenCode seed is not an OpenCode profile".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut profile = seed;
|
||||
profile.id = fresh_profile_id(&*self.ids, &existing)?;
|
||||
profile.name = match input.name {
|
||||
@ -227,7 +239,11 @@ impl CloneOpenCodeProfileFromSeed {
|
||||
None => format!("{} copy", profile.name),
|
||||
};
|
||||
if let Some(config) = input.opencode {
|
||||
// Honour the `opencode_backend_is_consistent` invariant (#97):
|
||||
// switching the clone to a local llamacpp backend evicts any cloud
|
||||
// backend carried by the seed (mirrors `AgentProfile::with_opencode`).
|
||||
profile.opencode = Some(config);
|
||||
profile.opencode_provider = None;
|
||||
}
|
||||
|
||||
self.store.save(&profile).await?;
|
||||
|
||||
Reference in New Issue
Block a user