(rawSlug: string, input?: OpenApiUpdateSpecInput)
| 933 | // the curated description, connections, and policies are all untouched - |
| 934 | // this is the "spec changed upstream" path, not a re-add. |
| 935 | const updateSpec = (rawSlug: string, input?: OpenApiUpdateSpecInput) => |
| 936 | Effect.gen(function* () { |
| 937 | const slug = IntegrationSlug.make(rawSlug); |
| 938 | const record = yield* ctx.core.integrations.get(slug); |
| 939 | const current = record ? decodeOpenApiIntegrationConfig(record.config) : null; |
| 940 | if (!record || !current) { |
| 941 | return yield* new IntegrationNotFoundError({ slug }); |
| 942 | } |
| 943 | |
| 944 | // The new spec source: explicit input wins; otherwise re-fetch from |
| 945 | // where the spec originally came from. A pasted-blob integration has |
| 946 | // no origin, so updating it requires a new input. |
| 947 | const nextOverrides = input?.specOverrides ?? current.specOverrides ?? []; |
| 948 | const storedSourceHash = current.sourceSpecHash ?? current.specHash; |
| 949 | const storedSourceText = |
| 950 | input?.spec === undefined && !current.specUrl && input?.specOverrides !== undefined |
| 951 | ? storedSourceHash |
| 952 | ? yield* ctx.storage.getSpec(storedSourceHash) |
| 953 | : null |
| 954 | : null; |
| 955 | const specInput: OpenApiSpecInput | null = |
| 956 | input?.spec ?? |
| 957 | (current.specUrl |
| 958 | ? { kind: "url", url: current.specUrl } |
| 959 | : storedSourceText |
| 960 | ? { kind: "blob", value: storedSourceText } |
| 961 | : null); |
| 962 | if (specInput === null) { |
| 963 | return yield* new OpenApiParseError({ |
| 964 | message: |
| 965 | "This integration's spec was pasted inline and has no source URL to re-fetch. Provide the updated spec content.", |
| 966 | }); |
| 967 | } |
| 968 | |
| 969 | // Resolve + compile BEFORE the transaction (same Hyperdrive-deadlock |
| 970 | // rule as addSpec: never hold BEGIN across a network fetch). |
| 971 | const resolved = yield* resolveSpecForInput( |
| 972 | { |
| 973 | spec: specInput, |
| 974 | specFormat: current.specFormat, |
| 975 | specOverrides: nextOverrides, |
| 976 | headers: current.headers, |
| 977 | queryParams: current.queryParams, |
| 978 | baseUrl: current.baseUrl, |
| 979 | }, |
| 980 | httpClientLayer, |
| 981 | ); |
| 982 | const compiled = resolved.keepPathItem |
| 983 | ? undefined |
| 984 | : yield* compileOpenApiSpec(resolved.specText); |
| 985 | |
| 986 | const previousOperations = yield* ctx.storage.listOperations(rawSlug); |
| 987 | const previousNames = new Set(previousOperations.map((op) => op.toolName)); |
| 988 | |
| 989 | // The resolved spec text lives in the plugin blob store keyed by its |
| 990 | // content hash (`spec/<hash>`); the config carries only the hash. Put |
| 991 | // the blob outside the transaction - re-puts are idempotent and an |
| 992 | // aborted config update just leaves an unreferenced blob. |
nothing calls this directly
no test coverage detected