(rawSlug: string, input?: OpenApiUpdateSpecInput)
| 760 | // the curated description, connections, and policies are all untouched - |
| 761 | // this is the "spec changed upstream" path, not a re-add. |
| 762 | const updateSpec = (rawSlug: string, input?: OpenApiUpdateSpecInput) => |
| 763 | Effect.gen(function* () { |
| 764 | const slug = IntegrationSlug.make(rawSlug); |
| 765 | const record = yield* ctx.core.integrations.get(slug); |
| 766 | const current = record ? decodeOpenApiIntegrationConfig(record.config) : null; |
| 767 | if (!record || !current) { |
| 768 | return yield* new IntegrationNotFoundError({ slug }); |
| 769 | } |
| 770 | |
| 771 | // The new spec source: explicit input wins; otherwise re-fetch from |
| 772 | // where the spec originally came from. A pasted-blob integration has |
| 773 | // no origin, so updating it requires a new input. |
| 774 | const specInput: OpenApiSpecInput | null = |
| 775 | input?.spec ?? (current.sourceUrl ? { kind: "url", url: current.sourceUrl } : null); |
| 776 | if (specInput === null) { |
| 777 | return yield* new OpenApiParseError({ |
| 778 | message: |
| 779 | "This integration's spec was pasted inline and has no source URL to re-fetch. Provide the updated spec content.", |
| 780 | }); |
| 781 | } |
| 782 | |
| 783 | // Resolve + compile BEFORE the transaction (same Hyperdrive-deadlock |
| 784 | // rule as addSpec: never hold BEGIN across a network fetch). |
| 785 | const resolved = yield* resolveSpecForInput(specInput, httpClientLayer); |
| 786 | const compiled = yield* compileOpenApiSpec(resolved.specText); |
| 787 | |
| 788 | const previousOperations = yield* ctx.storage.listOperations(rawSlug); |
| 789 | const previousNames = new Set(previousOperations.map((op) => op.toolName)); |
| 790 | const nextNames = new Set(compiled.definitions.map((def) => def.toolPath)); |
| 791 | |
| 792 | // The resolved spec text lives in the plugin blob store keyed by its |
| 793 | // content hash (`spec/<hash>`); the config carries only the hash. Put |
| 794 | // the blob outside the transaction - re-puts are idempotent and an |
| 795 | // aborted config update just leaves an unreferenced blob. |
| 796 | const specHash = yield* sha256Hex(resolved.specText); |
| 797 | yield* ctx.storage.putSpec(specHash, resolved.specText); |
| 798 | yield* ctx.storage.putDefs(specHash, JSON.stringify(compiled.hoistedDefs)); |
| 799 | |
| 800 | const nextConfig: OpenApiIntegrationConfig = { |
| 801 | ...current, |
| 802 | specHash, |
| 803 | ...(specInputToSourceUrl(specInput) !== undefined |
| 804 | ? { sourceUrl: specInputToSourceUrl(specInput) } |
| 805 | : {}), |
| 806 | }; |
| 807 | |
| 808 | yield* ctx.transaction( |
| 809 | Effect.gen(function* () { |
| 810 | yield* ctx.core.integrations.update(slug, { |
| 811 | config: nextConfig satisfies OpenApiIntegrationConfig as IntegrationConfig, |
| 812 | }); |
| 813 | yield* ctx.storage.putOperations( |
| 814 | rawSlug, |
| 815 | openApiStoredOperationsFromCompiled(rawSlug, compiled), |
| 816 | ); |
| 817 | }), |
| 818 | ); |
| 819 |
nothing calls this directly
no test coverage detected