Skip to content

Give tool catalogs a real sync lifecycle - #1612

Draft
RhysSullivan wants to merge 8 commits into
fix/tools-sync-read-pathfrom
fix/tools-sync-lifecycle
Draft

Give tool catalogs a real sync lifecycle#1612
RhysSullivan wants to merge 8 commits into
fix/tools-sync-read-pathfrom
fix/tools-sync-lifecycle

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1605.

Tool catalogs previously had one timestamp doing every job: tools_synced_at was stamped even when a listing failed (a month-dead MCP server read as freshly synced and was re-dialled every 15 minutes forever — 443 handshakes/day fleet-wide fail with 401 alone), markToolsStale destroyed the stamp to signal drift, and concurrent reads across isolates duplicated refresh work with no coordination.

This gives connections an explicit lifecycle, derived from columns (no state enum):

  • tools_stale_token — drift is an opaque nonce, not a timestamp. markToolsStale writes a fresh token; an authoritative listing clears it with a compare-and-set on the value it observed, inside the persist transaction. A drift signal landing mid-listing carries a different token and survives. No clock comparisons anywhere.
  • tools_sync_claim_id/_claim_at — a leased (60s) compare-and-set claim dedupes refreshes across isolates; fumadb has no row counts, so claim = conditional update + read-back.
  • tools_sync_failures/_retry_at — failed or incomplete listings walk a jittered exponential ladder (TTL × 2^(n−1), 6h cap) instead of retrying on every read. tools_synced_at is only ever stamped by an authoritative listing, with the listing's start instant.
  • tools_sync_error_kind — plugins classify incomplete listings (auth/unreachable/protocol/config); auth-dead connections with a catalog to serve park (expired only) until any explicit signal (refresh, config revision, credential change, healthy probe) or ladder-driven retry for cold ones.
  • Refresh outcomes are a three-way synced/incomplete/lost_claim — a broken fleet no longer reports as healthy syncs.
  • MCP: secret-store outages no longer dial unauthenticated or park as auth-dead; the liveness classifier is structural (no message matching). OpenAPI: an unreadable config preserves the catalog instead of authoritatively wiping it.

One migration (0016), additive nullable columns only. TTL-expired refresh still runs inline (filter-scoped, claimed); moving it off the read path is the next PR.

Replace connection.tools_stale_at (bigint ms) with tools_stale_token
(nullable text). The column answers "was this catalog invalidated since
the listing now finishing began" — a version question, which a wall clock
answers only as precisely as it ticks. markToolsStale writes a fresh
token; an authoritative listing captures the token it observed at its
start and clears it by compare-and-set inside replaceCatalog's existing
transaction, so a mark that landed mid-listing carries a different token
and survives. A missed clear fails closed and converges on the next
re-list.

This restores the read scan's zero-rows steady state: nothing ever
cleared the timestamp column, so the prefilter's drift arm re-selected
every once-drifted connection on every read forever.

Contract change from a1fdc72: the same-millisecond stale/synced tie is
gone rather than resolved either way, so the schedule tests that pinned
inclusive comparison now pin token presence instead. Deliberate.

Also:
- produceConnectionTools returns outcome synced/incomplete/lost_claim
  instead of a boolean, with its own counter and span attribute. A plugin
  reporting bad news correctly is no longer counted as a sync, nor as a
  refresh that crashed. writeSyncOutcome reports whether it wrote, so an
  incomplete listing that had also lost its lease reports lost_claim.
- The auth park gates expired only. A never-synced parked connection has
  no catalog to serve, so it walks the retry ladder instead, which caps
  the waste and restores recovery for credentials repaired upstream.
- markToolsStale runs under the catalog persist lock; it was the one
  lifecycle write left exposed to enrollment in another fiber's open
  transaction.

Migration 0016 and its snapshot are edited in place: unpushed, never run.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 2fb74bd Aug 16 2026, 04:08 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 2fb74bd Commit Preview URL

Branch Preview URL
Aug 16 2026, 04:07 PM

@RhysSullivan RhysSullivan changed the title fix/tools sync lifecycle Give tool catalogs a real sync lifecycle Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare preview

Console https://executor-preview-pr-1612.executor-e2e.workers.dev
MCP https://executor-preview-pr-1612.executor-e2e.workers.dev/mcp
Deployed commit 2fb74bd

Sign-in is Cloudflare Access (one-time PIN to an allowed email). The preview has its own database and encryption key; it is destroyed when this PR closes.

@pkg-pr-new

pkg-pr-new Bot commented Aug 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1612

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1612

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1612

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1612

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1612

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1612

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1612

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1612

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1612

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1612

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1612

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1612

executor

npm i https://pkg.pr.new/executor@1612

commit: 2fb74bd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant