(rawSlug: string, input?: OpenApiUpdateSpecInput)
| 859 | // the curated description, connections, and policies are all untouched - |
| 860 | // this is the "spec changed upstream" path, not a re-add. |
| 861 | const updateSpec = (rawSlug: string, input?: OpenApiUpdateSpecInput) => |
| 862 | Effect.gen(function* () { |
| 863 | const slug = IntegrationSlug.make(rawSlug); |
| 864 | const record = yield* ctx.core.integrations.get(slug); |
| 865 | const current = record ? decodeOpenApiIntegrationConfig(record.config) : null; |
| 866 | if (!record || !current) { |
| 867 | return yield* new IntegrationNotFoundError({ slug }); |
| 868 | } |
| 869 | |
| 870 | // The new spec source: explicit input wins; otherwise re-fetch from |
| 871 | // where the spec originally came from. A pasted-blob integration has |
| 872 | // no origin, so updating it requires a new input. |
| 873 | const specInput: OpenApiSpecInput | null = |
| 874 | input?.spec ?? (current.specUrl ? { kind: "url", url: current.specUrl } : null); |
| 875 | if (specInput === null) { |
| 876 | return yield* new OpenApiParseError({ |
| 877 | message: |
| 878 | "This integration's spec was pasted inline and has no source URL to re-fetch. Provide the updated spec content.", |
| 879 | }); |
| 880 | } |
| 881 | |
| 882 | // Resolve + compile BEFORE the transaction (same Hyperdrive-deadlock |
| 883 | // rule as addSpec: never hold BEGIN across a network fetch). |
| 884 | const resolved = yield* resolveSpecForInput( |
| 885 | { |
| 886 | spec: specInput, |
| 887 | specFormat: current.specFormat, |
| 888 | headers: current.headers, |
| 889 | queryParams: current.queryParams, |
| 890 | baseUrl: current.baseUrl, |
| 891 | }, |
| 892 | httpClientLayer, |
| 893 | ); |
| 894 | const compiled = resolved.keepPathItem |
| 895 | ? undefined |
| 896 | : yield* compileOpenApiSpec(resolved.specText); |
| 897 | |
| 898 | const previousOperations = yield* ctx.storage.listOperations(rawSlug); |
| 899 | const previousNames = new Set(previousOperations.map((op) => op.toolName)); |
| 900 | |
| 901 | // The resolved spec text lives in the plugin blob store keyed by its |
| 902 | // content hash (`spec/<hash>`); the config carries only the hash. Put |
| 903 | // the blob outside the transaction - re-puts are idempotent and an |
| 904 | // aborted config update just leaves an unreferenced blob. |
| 905 | const specHash = yield* sha256Hex(resolved.specText); |
| 906 | yield* ctx.storage.putSpec(specHash, resolved.specText); |
| 907 | if (compiled) { |
| 908 | yield* ctx.storage.putDefs(specHash, JSON.stringify(compiled.hoistedDefs)); |
| 909 | } |
| 910 | |
| 911 | const nextConfig: OpenApiIntegrationConfig = { |
| 912 | ...current, |
| 913 | specHash, |
| 914 | ...((resolved.specUrl ?? specInputToSpecUrl(specInput)) !== undefined |
| 915 | ? { specUrl: resolved.specUrl ?? specInputToSpecUrl(specInput) } |
| 916 | : {}), |
| 917 | }; |
| 918 |
nothing calls this directly
no test coverage detected