Dependency Bots Find New Versions. Synaptic Finds What Breaks.
A version bump is a notification. An API migration is work. Synaptic API Maintenance detects source-grounded breaking changes, joins them to your actual call sites through the code graph, and opens one verified draft PR or MR.

Dependency bots find new versions. Synaptic finds what breaks.
Every repository that has run a dependency bot for a year knows the two piles. The first pile is patch and minor bumps that merge themselves. The second pile is the major versions nobody opens, because opening one means finding out, at review time, how much of the codebase actually touched that vendor.
Synaptic API Maintenance is built for the second pile. It reads the vendor's own contract, diffs it into breaking events, joins each event to the exact places your code calls the changed operation, generates a bounded repair in an isolated worktree with the network disabled, verifies it against your real builds and tests, and opens or updates exactly one draft pull request or merge request. It never auto-merges.
It runs in three places: always-on in Synaptic Cloud, interactively in the VS Code and JetBrains extensions, and unattended in GitHub Actions or GitLab CI.
The question a dependency bot answers
Dependabot and Renovate answer one question, and answer it well: a newer version of this package exists, and here is the manifest change that adopts it. That is genuinely valuable. Security advisories reach you quickly, lockfiles stay current, and the long tail of harmless bumps stops consuming attention.
But the manifest is the whole input. A version-bump bot reads what you declared you depend on, not what you actually call. So the questions it cannot reach are the ones that make an upgrade expensive:
| Question | Version-bump bot | Synaptic |
|---|---|---|
| Is there a newer version? | Yes | No, keep your bump bot |
| Did the vendor's API change in a breaking way? | Inferred from the version number | Diffed from the vendor's contract |
| Do we call the part that changed? | Unknown | Resolved against the code graph |
| Which wrappers and tests are downstream of it? | Unknown | Selected by reverse impact |
| Does the migration compile and pass? | CI finds out after review | Verified before the draft opens |
The practical difference is what arrives in your queue. A version-bump bot opens a PR because a number changed, and the semantics are discovered later by whoever is on review rotation. Synaptic opens a change request only after it has established that the change is applicable to code you have, and that a patch for it passes your own gates.
This is not a replacement for version-bump automation, and the rest of this article does not pretend otherwise. Keep it. The two answer different questions, and running both is the point.
Detection is source-grounded, not prose-grounded
The input to a breaking event is the vendor's machine-readable contract. Contract readers auto-detect by content and cover OpenAPI, AsyncAPI, GraphQL SDL and introspection, Protobuf and gRPC source, WSDL, Smithy, and OpenRPC. Formats without a complete native compatibility policy are marked partial and review-only rather than being treated as authoritative.
Sources are declared per vendor and fetched under explicit limits:
[[vendors]]
id = "stripe"
enabled = true
packages = ["npm:stripe", "pypi:stripe"]
hosts = ["api.stripe.com"]
auto_repair_confidence = 0.92
[[vendors.sources]]
kind = "open_api"
uri = "https://vendor.example/openapi.json"
affected_versions = ">=10.0.0,<20.0.0"
max_bytes = 10485760
min_poll_interval_seconds = 300
Remote sources use conditional requests, explicit content types, byte caps, per-source poll intervals, and an integrity lock. Fetching applies SSRF and redirect defenses over bounded streams.
Changelogs are accepted, but they are prose, and prose is treated as such. Release HTML, scripts, and command-shaped instructions are stripped, and a changelog on its own can produce only a review candidate. It takes structured evidence to corroborate it into an event. Vendor contracts, changelogs, agent output, project commands, Git state, and provider metadata are all treated as untrusted input throughout.
For vendors that push rather than publish, webhook reads a bounded local
envelope carrying schema, vendor, revision, occurred_at,
content_type, content_digest, and the contract itself. The vendor must
match a configured source, the content type must be JSON or YAML, unknown
fields are rejected, and the BLAKE3 digest of the embedded contract must match
before anything enters normalization.
Applicability comes from the graph, not the manifest
This is the part a manifest cannot do.
Synaptic already builds a graph of your repository — symbols and how they call, import, inherit, and cross language boundaries. API Maintenance adds three vendor-neutral relations on top of it:
| Source | Relation | Target | Traversed for impact |
|---|---|---|---|
| Code symbol | uses_api | api_operation | Yes |
api_operation | provided_by | api_vendor | No |
| Package node | sdk_for | api_vendor | No |
Bindings are deliberately strict. A direct HTTP binding requires a literal absolute URL plus a configured host, method, and path. An SDK binding requires an imported package plus either a static member mapping or exact generator-supplied vendor, protocol, method, path, and operation metadata. Similar names and computed member access stay unresolved rather than being guessed at:
[[vendors.sdk_bindings]]
package = "maven:com.stripe:stripe-java"
imports = ["com.stripe"]
member = "StripeClient.v1.customers.create"
method = "POST"
path = "/v1/customers"
SDK call extraction spans more than twenty package ecosystems — npm JavaScript
and TypeScript including Vue, Svelte and Astro scripts, PyPI, Go modules, Cargo,
Maven and Gradle across Java, Kotlin, Groovy and Scala, NuGet, Composer,
RubyGems, SwiftPM, Hex, Dart pub, and on down the tail. For ecosystems whose
registry coordinate differs from the namespace used in source, imports
declares the mapping. Namespace matching is segment-aligned, and ambiguity fails
closed.
That strictness is what makes the applicability labels worth reading. An event
reaches auto-repair only after vendor, resolved version, observed usage,
confidence, and allowed-scope gates all pass. An SDK you depend on but never
call, or an installed version outside the affected range, produces
not_applicable and generates no work. An unknown version or ambiguous
ownership produces review_required and asks for a human instead of guessing.
When an event is applicable, reverse impact starts at only the changed operation nodes and expands to wrappers, callers, tests, historical co-changes, repository memory, and known dynamic-dispatch hazards. The result is a repair brief with hard file, source-byte, evidence, and graph-node budgets, so what reaches a model is bounded by construction.
Coverage is the honesty check
Applicability is only trustworthy if you can see what Synaptic can and cannot
observe. api coverage reports that directly, and it needs no vendor
configuration at all — it reads the existing graph, package manifests and
SBOMs, and optional evidence, then reports every literal external HTTP call,
SDK call, non-HTTP boundary, service record, exact binding, and dynamic-dispatch
hazard it can currently see.
Each external surface lands on one of three rungs:
observed— Synaptic can see the surface but the provider, model, source, binding, or version is still a gap.identified— a configured owner exists, but there is no exact operation mapping yet.bound— an existing high-confidenceuses_apiedge reaches a specific operation.
complete means the evidence present in that graph has no unresolved coverage
gaps. It does not mean unexecuted dynamic behavior or an undocumented provider
change has been proven absent, and the tool does not say otherwise. Use
--require-complete in policy or CI to fail while any gap remains.
Every extraction also writes contract-discovery.json and a disabled,
report-only candidate-profile.toml of inferred vendor identities. Discovery
never enables monitoring by itself; promoting an entry into
.synaptic/api-maintenance.toml is a reviewed, deliberate act.
Why the patch is worth reviewing
A generated diff is only useful if the pipeline around it is skeptical. Five properties do that work.
The baseline runs first. Before any candidate is generated, the isolated base worktree runs the same relevant project checks and tests that will judge the patch. If they already fail, or the tooling is unavailable, the run stops as a failure — a pre-existing red build is never blamed on a generated patch.
Verification plans itself. api check-plan recursively inventories every
independently verifiable project rather than stopping at the first root marker,
across roughly thirty build systems — Cargo, Go modules, pytest, the npm family,
Gradle and Maven, .NET solutions, SwiftPM, Composer, Bundler, Mix, CMake and
CTest, Make, and the rest. Native workspaces and solutions own their child
projects, so nothing is built twice. Package-manager scripts are read from their
manifests, and an npm placeholder test is not accepted as a real suite.
Unresolved capabilities are reported as localized gaps, and a relevant
unresolved gap makes the run inconclusive — never a pass.
Generation has no network. Patch generation and every project command run with the network disabled. A platform network guard is mandatory and passed as explicit argv; Synaptic fails closed if none is configured rather than assuming an environment is isolated because credentials happened to be scrubbed. The agent contract is deliberately narrow — it reads the immutable brief and emits only a diff and a rationale:
{"unified_diff":"diff --git ...","rationale":"why this is the minimal migration"}
Retries are bounded to three, and each one receives only the immutable brief, the prior patch, and a bounded failure report.
Five gate groups must all pass. Patch application and path, size, and
permission policy; incremental and full graph parity plus API binding
invariants; graph-selected tests and every relevant detected or configured
build; configured lint, schema, integration, and security policy commands; and
a final risk forecast, cycle detection, and public-API preservation check.
inconclusive is not success at any of them.
Patch policy is restrictive by default. Path traversal, symlink escape,
submodules, binaries, executable-bit changes, secrets, protected workflow,
ownership and security-policy files, unrelated generated artifacts, and
unreasoned scope expansion are all rejected. The engine refuses to patch
.github/workflows/, .gitlab-ci.yml, .gitlab/, CODEOWNERS, credential
files, and security policy regardless of what an allowlist says.
The scope ceiling is yours to set, and it is small on purpose:
max_files = 12
max_changed_lines = 800
max_attempts = 3
max_risk_score = 80
allowed_paths = ["src/", "tests/"]
require_tests = true
Three ways to run it
In Synaptic Cloud
Cloud collects API evidence on every snapshot with no configuration at all. Coverage, dependency inventory, contract discovery, and a safe offline scan run as part of ordinary extraction, so the API Maintenance panel shows your detected SDK and package dependencies, their versions, scopes, and source manifests from the first snapshot onward.
Source-changing policy is separate and explicit. Each project sits at off,
report_only, or draft_change_request, optionally scoped to specific
vendors. The panel shows provider, vendor, event, base SHA, engine identity,
the stage timeline, verification gate results, and the draft PR or MR link, and
it names the states that are not successes — no-change, review-required,
stale-base, failed, and inconclusive — instead of collapsing them into an error
badge. Manual dispatch and retry are permission-gated and rate-limited, and
every state transition is an audit event.
The trust boundary is the notable part. Cloud stores run metadata and digests.
It does not receive the patch, the handoff bundle, OIDC tokens, provider
responses, or model credentials. CI reports back over OIDC-authenticated,
forward-only callbacks bound to provider, repository identity, workflow
identity, immutable commit SHA, policy digest, and engine digest; replay
receipts retain only a hashed jti. Dispatch credentials are operation-scoped
and separate from publication credentials — GitHub dispatch mints a
repository-scoped installation token requesting only Actions: write, and
GitLab dispatch uses a distinct encrypted pipeline trigger token that is not
the publication token.
Each panel also deep-links into the matching IDE workflow, because the operations that touch source belong where the source is.
In the IDE
The VS Code extension and JetBrains plugin are the interactive, source-bearing boundary. Synaptic: API Maintenance opens the full pipeline as a menu: initialize configuration, inventory SDKs, measure coverage, discover contracts, preview the verification plan, scan sources, evaluate event impact, repair, verify a run, publish, run the composed workflow, and export or import a verified run handoff.
The interactive affordances are where this beats a PR-only workflow. Repair offers three modes — a dry-run brief, an externally supplied candidate patch, or full isolated repair — so you can read what the model would be asked before anything is generated. Coverage accepts sanitized OTLP runtime evidence and versioned behavioral evidence from a file picker, and can be set to require complete coverage. Scan runs against checked-in offline sources or configured network sources. Impact takes an event ID and an optional repair path allowlist.
Execution is deliberately boring: an absolute engine path, an absolute opened
repository root, argv arrays with shell: false, bounded output, cancellation,
and a thirty-minute default deadline. Graph-sensitive operations refresh the
local graph first. Full repair fails closed unless the configured agent command
contains {request} and you supply an ordered network-isolation guard. Publish
is a distinct operation behind its own confirmation modal and accepts only
fully verified runs. The local MCP server enables --allow-memory-write and
nothing else — never --allow-exec, HTTP, or hosted credentials.
In GitHub Actions and GitLab CI
Unattended runs use one provider-neutral runner contract,
synaptic-api-maintenance/v1, implemented identically for both providers and
split into two credential-separated stages.
The repair stage checks out an immutable base SHA with credential persistence off, verifies the exact engine binary digest, extracts the graph, checks coverage and the verification plan, and runs the engine with deferred publication. Candidate generation, patch application, and verification all run under the configured no-network guard. It exports one thing: a checksummed verified-run handoff plus bounded manifest metadata. It holds no repository write credential at any point.
The publish stage starts from a fresh checkout of the same base SHA, verifies the engine and manifest digests, rejects a moved target branch, imports and revalidates each handoff, then creates or updates exactly one draft change request. It never executes repository build or test commands.
On GitHub, set the runner variables and pin the engine by digest:
SYNAPTIC_BINARY: /usr/local/bin/synaptic
SYNAPTIC_ENGINE_SHA256: <lowercase sha256 of that exact executable>
SYNAPTIC_CLOUD_URL: https://synapticgraph.com
SYNAPTIC_CLOUD_POLICY_ID: <policy id from the API Maintenance panel>
The repair job holds only contents: read and id-token: write. Publication
prefers a dedicated GitHub App installed on the target repository with
Contents: Read and write and Pull requests: Read and write, passed as the
reusable-workflow secrets github_app_id and github_app_private_key; the
pinned token action requests only those two permissions and scopes the
installation token to the current repository. Without those secrets it falls
back to the job-scoped GITHUB_TOKEN. The publish job sits behind the
synaptic-api-publisher environment and runs only for a verified handoff in
draft_change_request mode.
On GitLab, include the template pinned to a reviewed tag or commit rather than a moving branch:
include:
- project: your-group/synaptic-platform
ref: <reviewed-release-tag-or-commit-sha>
file: /.gitlab/ci/synaptic-api-maintenance.yml
Publication uses a masked, protected project access token with Developer or
higher access, api for merge-request operations and write_repository for
the branch push. The repair job explicitly removes CI_JOB_TOKEN,
SYNAPTIC_GITLAB_PUBLISH_TOKEN, GITLAB_TOKEN, and GH_TOKEN from its
environment before extraction or repair, so the isolation is enforced rather
than assumed.
Both providers converge on the same terminal vocabulary. Repair finishes as
no_change, not_applicable, review_required, verified, failed, or
inconclusive. Publication adds published, stale_base, and canceled. A
stale base is a safe terminal result, not an error: nothing is pushed, and the
run is repeated against the new head.
Idempotency is structural. The branch name is deterministic —
synaptic/api/<vendor>/<event-prefix> — and carries a hidden event and base
marker, so a rerun updates the existing draft instead of opening a second one.
A run is keyed by repository identity, base SHA, event ID, and policy digest.
If more than one open change request matches a marker, publication fails closed
rather than picking one. Generated branches run your ordinary PR and MR
validation, and cannot recursively trigger API maintenance.
What it deliberately does not do
An honest list matters as much as the feature list.
- It does not replace your version-bump bot. Advisory-driven patching and routine minor upgrades are a different job. Run both.
- It does not auto-merge, approve, or bypass branch protection. Every provider mutation is a draft. There is no configuration that changes this.
- It does not guess bindings. Similar names, computed member access, and ambiguous namespaces stay unresolved. That means Synaptic will sometimes tell you it cannot see something, which is the point of the coverage ladder.
- It does not claim absence of risk.
completecoverage means the evidence in that graph has no unresolved gaps, not that an undocumented provider change or unexecuted dynamic path has been ruled out. - It does not treat vendors as trusted. Contracts, changelogs, agent output, project commands, Git state, and provider metadata are all untrusted input, and logs, PR bodies, and worker events are bounded and redact secret-shaped content.
- It does not touch its own controls. Workflow, CI, ownership, credential, and security-policy files are rejected by the patch validator independently of the configured allowlist.
Getting started
Locally, the read-only stages are safe to run immediately. None of init,
inventory, scan, impact, or coverage modifies repository source or
touches a provider, and repair writes only inside an isolated worktree:
synaptic api init
synaptic extract .
synaptic api coverage --json
synaptic api discover --json
synaptic api check-plan --root . --json --require-complete
Read the coverage report before configuring a single vendor. It tells you what your external surface actually looks like, which providers are unidentified, and where the evidence gaps are — and it is the same report Cloud shows you on every snapshot.
Then commit the policy and keep generated state out of version control:
synaptic-out/
.synaptic/api-maintenance/
.synaptic/runtime-evidence/
.synaptic/behavioral-evidence/
Start each repository in report_only. Let it observe real vendor changes,
confirm the applicability labels match what you would have concluded by hand,
and check that the verification plan finds every project you expect. Then move
one repository, one provider, and one vendor to draft_change_request and
widen from there.
The first draft PR that arrives will not be a version bump. It will be a migration, with the call sites it changed, the tests that covered them, and the gates it passed.
Summary
Version-bump automation solved the notification problem a decade ago and solved it well. It did not solve the migration problem, because a manifest cannot say which of your call sites a vendor just broke.
Synaptic answers that question from evidence it can defend: the vendor's own contract for what changed, the code graph for whether it reaches you, your real builds and tests for whether the fix holds, and a draft change request as the only thing it is ever allowed to do to your repository.


