diff --git a/frontend/src/lib/components/AppConnectInner.svelte b/frontend/src/lib/components/AppConnectInner.svelte index a461a6cb64..a9a0837d1f 100644 --- a/frontend/src/lib/components/AppConnectInner.svelte +++ b/frontend/src/lib/components/AppConnectInner.svelte @@ -760,18 +760,29 @@ // of another type sitting where the project wanted one of ours would otherwise have // its value replaced with credentials for a different provider, while keeping its // own type — destroying a working resource that has nothing to do with the import. - let filling = exists && !!fillPath && path === fillPath + const filling = exists && !!fillPath && path === fillPath if (filling) { - const occupantType = await ResourceService.getResource({ - workspace: effectiveWorkspace, - path - }) - .then((r) => r?.resource_type) - .catch(() => undefined) - if (occupantType && occupantType !== resourceType) { + // Fails closed. Only a read that succeeds and answers with exactly this type + // permits the write — a failed read, a missing type, or any other type all + // refuse. Letting "could not tell" through is how the overwrite this guard + // exists to stop would happen anyway, on the one occasion the check was needed + // and could not run. + let occupantType: string | undefined + try { + occupantType = ( + await ResourceService.getResource({ workspace: effectiveWorkspace, path }) + )?.resource_type + } catch (e: any) { throw Error( - `Resource at path ${path} is a ${occupantType} resource, not ${resourceType}. ` + - `Move or rename it, then import again.` + `Could not read what is already at ${path} (${e?.body ?? e?.message ?? e}), ` + + `so it will not be written over. Try again.` + ) + } + if (occupantType !== resourceType) { + throw Error( + `Resource at path ${path} is ${ + occupantType ? `a ${occupantType} resource` : 'of an unknown type' + }, not ${resourceType}. Move or rename it, then import again.` ) } } diff --git a/frontend/src/lib/components/ImportSetupStep.svelte b/frontend/src/lib/components/ImportSetupStep.svelte index 16c704150b..ae22622bee 100644 --- a/frontend/src/lib/components/ImportSetupStep.svelte +++ b/frontend/src/lib/components/ImportSetupStep.svelte @@ -76,6 +76,12 @@ * shipped — and to make sure nothing offers to write over what is there. */ occupiedBy?: string + /** + * The resource could not be read, so nothing here knows whether it needs filling. Kept + * on the checklist rather than dropped: a read that fails is not evidence the resource + * is absent, and removing the row reports "all set" over a credential nobody filled. + */ + unreadable?: boolean } let loading = $state(true) @@ -268,8 +274,20 @@ if (found?.resource_type && found.resource_type !== r.resource_type) { occupiedBy = found.resource_type } - } catch { - continue // Not there — the import reported that failure already. + } catch (e: any) { + // A 404 is the import having failed to create it, which it reported itself. + // Any other failure is a read this could not complete, which says nothing about + // whether the resource is there or needs filling — so the row stays. + if (e?.status === 404) continue + out.push({ + path: r.path, + resourceType: r.resource_type, + missing: [], + done: false, + justSaved: false, + unreadable: true + }) + continue } const filled = new Set( value && typeof value === 'object' @@ -279,15 +297,21 @@ : [] ) let required: string[] = [] + // A type whose schema will not load leaves `required` empty, which reads as "nothing + // missing" — and a half-filled resource would drop off the checklist as done. The + // row is kept instead; it just cannot name which fields are short. + let requirementsUnknown = false try { const schema = (await ResourceService.getResourceType({ workspace, path: r.resource_type })) ?.schema as { required?: string[] } | undefined required = schema?.required ?? [] - } catch {} + } catch { + requirementsUnknown = true + } const missing = required.filter((k) => !filled.has(k)) // A conflicting occupant is always listed, however full its value looks: the row is // what tells the user the project is missing a resource it shipped. - if (occupiedBy || missing.length > 0 || filled.size === 0) { + if (occupiedBy || requirementsUnknown || missing.length > 0 || filled.size === 0) { out.push({ path: r.path, resourceType: r.resource_type, @@ -604,7 +628,8 @@ >