Skip to content

Speed up Erxes executor provisioning - #3

Open
darjss wants to merge 10 commits into
mainfrom
perf/erxes-provision-fast-path
Open

Speed up Erxes executor provisioning#3
darjss wants to merge 10 commits into
mainfrom
perf/erxes-provision-fast-path

Conversation

@darjss

@darjss darjss commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Batch D1 createMany inserts through Drizzle's batch() API so ~1,500 tool rows land in one round trip instead of ~227 sequential writes
  • Skip produceConnectionTools on returning /os/provision logins when the connection already has a synced, healthy catalog
  • Bundle offline OfficeNext GraphQL introspection at deploy time (no live introspection on first login)
  • Enable global_fetch_strictly_public on the Cloudflare host to stop same-zone 301 redirect loops

Deployed

  • executor.os.erxes.io — Worker version a0a4972f-69c5-4065-9e70-1a8eade2ef08

Test plan

  • Passwordless login via officenext → CF OS embed: first login completes in seconds, not ~90s
  • Log out and log in as same user: provision returns quickly (fast path skips tool sync)
  • Log in as different user: tools still available, credential rotates correctly
  • curl -X POST https://executor.os.erxes.io/os/provision with valid gatekeeper token still returns 200

Summary by Sourcery

Speed up Erxes executor provisioning by batching tool persistence, reusing healthy catalogs, and bundling GraphQL metadata ahead of first login.

New Features:

  • Add offline GraphQL introspection snapshots and an endpoint for attaching them to existing integrations.
  • Expose bounded GraphQL output shapes and provide agent guidance for explicit nested-field selections.
  • Support Cloudflare OS-authenticated /os/provision and /os/mcp routes for Erxes connections.

Bug Fixes:

  • Prevent same-zone Cloudflare redirect loops by enabling strictly public fetch behavior.
  • Avoid regenerating healthy, up-to-date connection tools when credentials are refreshed.
  • Resolve unique short tool names while reporting ambiguity instead of guessing between multiple matches.

Enhancements:

  • Batch SQLite/D1 inserts to reduce tool provisioning round trips.
  • Improve generated GraphQL defaults with bounded list-item scalar selections and support selection sets wrapped in outer braces.
  • Add read-only annotations to MCP utility tools.

Build:

  • Fetch and bundle the Erxes GraphQL introspection snapshot during the Cloudflare host build and deployment process.
  • Make Cloudflare deployment resources and configuration instance-specific with reusable secrets and dry-run support.

Deployment:

  • Provision and deploy tenant-scoped Cloudflare Workers, D1 databases, and R2 buckets with shared OS authentication secrets.

Documentation:

  • Document shared Cloudflare OS authentication for provisioning and MCP routes.

Tests:

  • Add coverage for GraphQL nested output shapes, bounded list defaults, custom selections, connection refresh behavior, short tool-path resolution, and skill guidance.

darjss added 10 commits August 8, 2026 16:25
List-of-object fields were omitted from the default selection, so wrappers
with totalCount succeeded with no rows. Short names from tools.search
404'd because the sandbox requires a five-segment address.
Use D1 batch inserts for tool catalog writes, skip redundant tool sync when a connection already has a fresh catalog, bundle offline GraphQL introspection for erxes-officenext, and enable global_fetch_strictly_public to avoid same-zone 301 loops.
@sourcery-ai

sourcery-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR accelerates executor provisioning by batching D1 inserts, reusing fresh connection catalogs, and bundling OfficeNext GraphQL introspection at deploy time; it also adds signed Cloudflare OS provisioning routes, instance-aware deployment tooling, bounded GraphQL result shapes and selection guidance, and unique short-name MCP tool resolution.

Sequence diagram for fast OfficeNext provisioning

sequenceDiagram
    participant OS as Cloudflare OS
    participant Worker as Cloudflare Worker
    participant Executor as Executor API
    participant D1 as D1 Database
    participant GraphQL as OfficeNext GraphQL

    OS->>Worker: POST /os/provision
    Worker->>Worker: verify Cloudflare OS JWT
    Worker->>Worker: loadErxesIntrospection()
    Worker->>Executor: GET /api/graphql/integrations/erxes-officenext
    alt integration has no snapshot
        Worker->>Executor: POST /api/graphql/integrations
        Worker->>Executor: POST /api/graphql/integrations/erxes-officenext/introspection
    end
    Worker->>Executor: POST /api/connections
    Executor->>D1: upsert connection
    alt fresh healthy catalog exists
        Executor->>D1: check existing tool row
        Executor-->>Worker: reuse catalog and rotate credential
    else catalog missing or stale
        Executor->>GraphQL: introspect and produce tools
        Executor->>D1: batch tool inserts
        Executor-->>Worker: provisioned connection
    end
    Worker-->>OS: 200 response
Loading

Sequence diagram for GraphQL tool selection and invocation

sequenceDiagram
    participant Agent
    participant MCP as MCP Tool Server
    participant Executor
    participant GraphQL as GraphQL API

    Agent->>MCP: skills({ name: "graphql" })
    MCP-->>Agent: selection guidance
    Agent->>MCP: tools.describe.tool({ path })
    MCP->>Executor: resolveSandboxToolPath(path)
    Executor-->>MCP: qualified tool path and output shape
    Agent->>MCP: tool({ select: "list { _id name } totalCount" })
    MCP->>Executor: execute(address, args)
    Executor->>GraphQL: query with explicit selection
    GraphQL-->>Executor: bounded nested result
    Executor-->>MCP: tool result
    MCP-->>Agent: selected fields
Loading

Flow diagram for batched D1 tool insertion

flowchart TD
    Start[produceConnectionTools creates tool rows] --> Split[Split rows into batches of up to 500]
    Split --> Limit[Group up to 1000 insert statements]
    Limit --> Batch[Drizzle batch]
    Batch --> D1[D1 single round trip per group]
    D1 --> Results[Collect returned tool IDs]
Loading

File-Level Changes

Change Details Files
Batch D1 tool-row creation to reduce executor provisioning round trips.
  • Detect D1’s batch API and group insert-returning statements into batches of up to 1,000.
  • Preserve sequential insertion for SQLite implementations without batch support and PostgreSQL.
packages/core/fumadb/src/adapters/drizzle/query.ts
Add a fast path for reusing healthy, up-to-date connection catalogs during credential rotation.
  • Check existing sync timestamps, integration revisions, health state, and tool presence before reproducing tools.
  • Update the connection value while retaining the existing catalog; reproduce tools when freshness cannot be established.
packages/core/sdk/src/executor.ts
packages/core/sdk/src/connections.test.ts
Move OfficeNext GraphQL introspection out of first-login provisioning and into deployment assets.
  • Fetch and validate the introspection schema during the Cloudflare build/deploy flow.
  • Bundle the snapshot as a Worker asset and attach it to the GraphQL integration before creating the user connection.
  • Add an API endpoint and extension logic to parse, persist, and generate operations from an attached snapshot, with a fallback for missing assets.
apps/host-cloudflare/package.json
apps/host-cloudflare/scripts/deploy.sh
apps/host-cloudflare/scripts/fetch-erxes-introspection.mjs
apps/host-cloudflare/src/worker.ts
packages/plugins/graphql/src/api/group.ts
packages/plugins/graphql/src/api/handlers.ts
packages/plugins/graphql/src/sdk/plugin.ts
Support authenticated Cloudflare OS provisioning and MCP routes independently of browser Access authentication.
  • Verify short-lived HS256 OS assertions using the shared secret and construct member principals from their claims.
  • Expose /os/provision and /os/mcp adapters while retaining the existing /mcp route.
  • Document the shared-secret deployment model and validate minimum secret length.
apps/host-cloudflare/src/auth/cloudflare-access.ts
apps/host-cloudflare/src/config.ts
apps/host-cloudflare/src/worker.ts
apps/host-cloudflare/README.md
Make Cloudflare deployments instance-aware and configure resources and routing per tenant.
  • Derive Worker, D1, R2, domain, and generated config names from instance variables, with an account ID guard.
  • Generate or reuse a protected secrets file, support dry runs, deploy the generated Wrangler config, and upload both secrets.
apps/host-cloudflare/scripts/deploy.sh
apps/host-cloudflare/wrangler.jsonc
Improve generated GraphQL tool shapes, selection defaults, and agent guidance for nested results.
  • Add bounded output schemas with recursive depth limits and permissive extra fields.
  • Select scalar leaves plus a capped one-level set of list-item scalars by default.
  • Allow caller-supplied select values, including outer-brace normalization, and document explicit nested selections in a new skill.
packages/plugins/graphql/src/sdk/plugin.ts
packages/plugins/graphql/src/sdk/invoke.ts
packages/core/execution/src/skills.ts
packages/core/execution/src/index.ts
packages/plugins/graphql/src/sdk/plugin.test.ts
packages/core/execution/src/skills.test.ts
.changeset/bright-graphql-results.md
Resolve unqualified MCP tool names when unique and report ambiguity without guessing.
  • List tools to match short names, dotted names, or qualified suffixes.
  • Dispatch and describe unique matches using their fully qualified paths.
  • Return actionable suggestions for ambiguous matches and cover the behavior with tests.
packages/core/execution/src/tool-invoker.ts
packages/core/execution/src/tool-invoker.test.ts
Mark selected built-in MCP tools as read-only.
  • Add read-only annotations to code execution, skill lookup, and elicitation tools.
packages/hosts/mcp/src/tool-server.ts
Add Cloudflare deployment and host configuration updates for the new provisioning flow.
  • Update build/deploy commands to include the introspection asset and generated configuration.
  • Change host configuration for the instance deployment and same-zone fetch behavior.
apps/host-cloudflare/package.json
apps/host-cloudflare/wrangler.jsonc

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ac757f0-d3b0-409e-b7c4-db429f1e5ace


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="apps/host-cloudflare/package.json" line_range="7" />
<code_context>
   "scripts": {
-    "build": "vite build && node scripts/assert-shell-asset.mjs",
+    "build": "node scripts/fetch-erxes-introspection.mjs && vite build && node scripts/assert-shell-asset.mjs && cp assets/erxes-introspection.json dist/erxes-introspection.json",
     "deploy": "vite build && node scripts/assert-shell-asset.mjs && wrangler deploy",
     "dev": "wrangler dev",
     "dev:web": "vite dev",
</code_context>
<issue_to_address>
**issue (broader_impact):** The package `deploy` script still runs only `vite build` and does not fetch or copy `erxes-introspection.json` into `dist`, so deployments through `bun run deploy` omit the offline snapshot and first connection provisioning falls back to live GraphQL introspection.

**Triggers:** When the Worker is deployed through the package's `deploy` script rather than the bespoke `scripts/deploy.sh` flow.

**Suggested fix:** Make `deploy` invoke the same snapshot-fetching build or explicitly run `fetch-erxes-introspection.mjs` and copy the asset before `wrangler deploy`.

```suggestion
    "deploy": "bun run build && wrangler deploy",
```
</issue_to_address>

### Comment 2
<location path="apps/host-cloudflare/src/worker.ts" line_range="111-122" />
<code_context>
+    return null;
+  }
+
+  const attached = await app(
+    executorRequest(
+      request,
+      `/api/graphql/integrations/${ERXES_INTEGRATION}/introspection`,
+      "POST",
+      {
+        introspectionJson,
+      },
+    ),
+  );
+  if (!attached.ok) return attached;
+  return null;
+};
+
</code_context>
<issue_to_address>
**issue (broader_impact):** When the existing `erxes-officenext` integration already exists, attaching the bundled snapshot does not update its endpoint from the current provision request. The connection is then created against `input.endpoint`, while generated GraphQL tools continue using the old integration endpoint.

**Triggers:** When the OfficeNext GraphQL endpoint changes between deployments or differs from the endpoint used by the first provision request.

**Suggested fix:** Update the integration endpoint when attaching the snapshot, or reject a conflicting endpoint explicitly.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and the new /os/* routes establish a separate HS256 bearer-token trust boundary and use its claims to authorize MCP access and persist user-provided Erxes cookies as connections; an error in that policy could allow forged identities or unauthorized access across the installation. Reverting the Worker would not revoke already-created connections or undo any access obtained while the route was active, and the deployment script also changes production resource and secret provisioning.

Blocking findings: apps/host-cloudflare/package.json:7, apps/host-cloudflare/src/worker.ts:122


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

"scripts": {
"build": "vite build && node scripts/assert-shell-asset.mjs",
"build": "node scripts/fetch-erxes-introspection.mjs && vite build && node scripts/assert-shell-asset.mjs && cp assets/erxes-introspection.json dist/erxes-introspection.json",
"deploy": "vite build && node scripts/assert-shell-asset.mjs && wrangler deploy",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): The package deploy script still runs only vite build and does not fetch or copy erxes-introspection.json into dist, so deployments through bun run deploy omit the offline snapshot and first connection provisioning falls back to live GraphQL introspection.

Triggers: When the Worker is deployed through the package's deploy script rather than the bespoke scripts/deploy.sh flow.

Suggested fix: Make deploy invoke the same snapshot-fetching build or explicitly run fetch-erxes-introspection.mjs and copy the asset before wrangler deploy.

Suggested change
"deploy": "vite build && node scripts/assert-shell-asset.mjs && wrangler deploy",
"deploy": "bun run build && wrangler deploy",

Comment on lines +111 to +122
const attached = await app(
executorRequest(
request,
`/api/graphql/integrations/${ERXES_INTEGRATION}/introspection`,
"POST",
{
introspectionJson,
},
),
);
if (!attached.ok) return attached;
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): When the existing erxes-officenext integration already exists, attaching the bundled snapshot does not update its endpoint from the current provision request. The connection is then created against input.endpoint, while generated GraphQL tools continue using the old integration endpoint.

Triggers: When the OfficeNext GraphQL endpoint changes between deployments or differs from the endpoint used by the first provision request.

Suggested fix: Update the integration endpoint when attaching the snapshot, or reject a conflicting endpoint explicitly.

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