Skip to content

test(sdk): concurrent policy creates get distinct positions - #1893

Merged
RhysSullivan merged 1 commit into
UsefulSoftwareCo:mainfrom
ra-co88:fix/policy-writes-transactional
Sep 15, 2026
Merged

RhysSullivan merged 1 commit into
UsefulSoftwareCo:mainfrom
ra-co88:fix/policy-writes-transactional

Conversation

@ra-co88

@ra-co88 ra-co88 commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

What

Trim to test-only per the approved proposal: the transaction wrap itself was superseded by upstream #1919 (caa0391), which wrapped policiesCreate/policiesUpdate in transaction(...). What this PR now carries is the one thing upstream still lacks: the discriminating regression test for that guarantee.

The test

it.live("concurrent creates of equally specific rules get distinct positions") — two equally specific policies.create calls run concurrently (Effect.all, concurrency "unbounded"); both must land, with distinct positions.

Verified load-bearing in both directions

  • With the wrap (upstream/main @ cc0fd8f): passes — positions distinct, both rules present.
  • Without the wrap (pre-Restrict workspace writes to admins #1919 executor swapped in): fails exactly as designed — the position Set size is 1, not 2. The duplicate-position race the transaction prevents is caught by this test.

Footprint

One file, +17 lines, no runtime changes, no changeset (test-only). Based on current upstream/main — merges clean.

@ra-co88

ra-co88 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up on the red E2E (cloud 13of16) check here: it's failing on main itself (e.g. the Version Packages runs), so it's pre-existing rather than from this PR. It's the cap-eviction scenario tripping over workerd resetting session Durable Objects mid-initialize when the test opens its burst of sessions — diagnosis and a proposed fix in #1895.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Verdict: needs changes (small; the fix itself is correct and wanted). Open 3 days.

Ran: bun run lint, bun run format:check, bun run typecheck — all pass on the branch. bun run --filter @executor-js/sdk test -- src/policies.test.ts src/executor.test.ts src/policy-transactional-visibility.test.ts — 69/69 pass. Merges clean onto current main.

Bug is real. A probe running two policies.create concurrently against makeTestExecutor (libSQL) on main lands both rows at position a0 — duplicate positions, so positionForNewPattern ordering is undefined. With this branch they serialize (Zz/a0). Same transaction(...) seam the connection upserts use (packages/core/sdk/src/executor.ts ~L3706, L4114), so the mechanism matches the codebase. No credential/token/connection-health paths touched.

Blocking issues:

  1. packages/core/sdk/src/policy-transactional-visibility.test.ts doesn't test the change: all 4 tests pass with main's executor.ts swapped in. The "concurrency proof" at the bottom is three sequential awaits, and its comment claiming simultaneous transactions fail with Failed query: BEGIN is not what happens — two concurrent transaction(...) calls on the sqlite adapter both succeed and serialize. The file also uses Effect.runPromise inside an Effect (flagged by the Effect language service) and a mid-file import { test }. Please delete it and add a discriminating case in packages/core/sdk/src/policies.test.ts instead (patch below — verified it fails on main, passes here).
  2. Changeset is a paragraph of internals; one sentence is the repo norm.
  3. The two new comment blocks in executor.ts are 5–7 lines each explaining the diff; one line is plenty.

Caveat for Rhys (not blocking): on Postgres the wrap gives atomicity but not the position-race fix — READ COMMITTED lets two transactions read the same row set and both insert the same position. On libSQL it works because the single connection serializes BEGIN. If the cloud path matters, this needs a per-owner lock (cf. catalogPersistLock semaphore, L3275) or a unique (owner, position) constraint; that's a follow-up, not this PR.

Pushed: nothing — the push proxy 403s on the fork (ra-co88/executor). Apply this on your branch (git apply), plus git rm packages/core/sdk/src/policy-transactional-visibility.test.ts:

diff --git a/.changeset/policy-transactional-visibility.md b/.changeset/policy-transactional-visibility.md
index 1391d77aa..d238d0968 100644
--- a/.changeset/policy-transactional-visibility.md
+++ b/.changeset/policy-transactional-visibility.md
@@ -2,18 +2,4 @@
 "@executor-js/sdk": patch
 ---
 
-fix: make tool-policy writes transactional
-
-`policiesCreate` and `policiesUpdate` previously ran their read-decide-write
-(existing-row scan → position computation → create, or existence check →
-update → re-read) as unsequenced statements. Two concurrent policy edits
-could interleave their reads and writes — both computing positions or
-updates from the same stale snapshot, silently overwriting each other or
-observing torn state.
-
-Both paths now run inside the same transaction wrapper the credential and
-integration upserts use (`fuma.transaction`, real BEGIN/COMMIT on
-libSQL/Postgres). Concurrent creates/updates serialize; each commits its
-own sequenced write, and an invocation's policy read at its call boundary
-sees committed state only — a revoked or blocked rule takes effect at the
-next invocation, never silently bypassed and never half-applied.
+Wrap tool-policy create and update in a transaction so concurrent edits can no longer read the same snapshot and commit duplicate positions or overwrite each other.
diff --git a/packages/core/sdk/src/executor.ts b/packages/core/sdk/src/executor.ts
index 0592a6ad3..51c58b5cd 100644
--- a/packages/core/sdk/src/executor.ts
+++ b/packages/core/sdk/src/executor.ts
@@ -5396,13 +5396,7 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea
           try: () => ownedKeys(input.owner),
           catch: (cause) => storageFailureFromUnknown("invalid owner", cause),
         });
-        // The read-decide-write (existing-row scan → specificity-aware
-        // position → create) runs inside ONE transaction so two concurrent
-        // policy creates can never interleave their scans and both commit a
-        // rule at the same position, or a create observe a torn sibling
-        // write. Same discipline as the credential/integration upserts:
-        // validation + ownership checks stay outside (no DB writes), the
-        // sequenced DB work is atomic.
+        // Scan → position → insert runs atomically so concurrent creates cannot commit duplicate positions.
         return yield* transaction(
           Effect.gen(function* () {
             const existing = yield* core.findMany("tool_policy", {
@@ -5444,11 +5438,7 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea
           });
         }
         const where = (b: AnyCb) => b.and(byOwner(input.owner)(b), b("id", "=", input.id));
-        // Existence check → update → re-read inside ONE transaction: a
-        // concurrent update cannot interleave between the existence check and
-        // the write, so two racing updates both land (sequenced commits) and
-        // neither observes the other's torn state. The returned row is the
-        // committed post-update row, never a stale pre-update projection.
+        // Existence check, write, and re-read commit together.
         return yield* transaction(
           Effect.gen(function* () {
             const existing = yield* core.findFirst("tool_policy", { where });
diff --git a/packages/core/sdk/src/policies.test.ts b/packages/core/sdk/src/policies.test.ts
index beb9703c4..c05e63484 100644
--- a/packages/core/sdk/src/policies.test.ts
+++ b/packages/core/sdk/src/policies.test.ts
@@ -428,6 +428,23 @@ describe("executor.policies", () => {
     }),
   );
 
+  it.live("concurrent creates of equally specific rules get distinct positions", () =>
+    Effect.gen(function* () {
+      const executor = yield* setupExecutor();
+      yield* Effect.all(
+        [
+          executor.policies.create({ owner: "org", pattern: "vercel.dns.create", action: "block" }),
+          executor.policies.create({ owner: "org", pattern: "vercel.dns.delete", action: "block" }),
+        ],
+        { concurrency: "unbounded" },
+      );
+
+      const rules = yield* executor.policies.list();
+      expect(rules).toHaveLength(2);
+      expect(new Set(rules.map((r) => r.position)).size).toBe(2);
+    }),
+  );
+
   it.effect("create stores rules at the requested owner", () =>
     Effect.gen(function* () {
       const executor = yield* setupExecutor();

@ra-co88

ra-co88 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Applied, thank you for the thorough review — especially for running the discriminating check against main's executor.ts; you're right that the old file's sequential awaits proved nothing about concurrency.

  • Deleted policy-transactional-visibility.test.ts and added the concurrent-creates case to policies.test.ts exactly per your patch (verified the new case fails on main and passes on this branch).
  • Changeset trimmed to one sentence.
  • Both executor.ts comment blocks reduced to one line.
  • bun run --filter @executor-js/sdk test -- src/policies.test.ts src/executor.test.ts — 66/66 green on the updated branch.

On the Postgres caveat for Rhys: agreed this PR is libSQL-scoped by mechanism. If the cloud path needs the position-race closed there, the per-owner lock (à la catalogPersistLock) or a unique (owner, position) constraint is the right follow-up — happy to take that in a separate PR if wanted.

@ra-co88

ra-co88 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded: the fix landed on main via 9ccef8e (fix: wrap tool-policy writes in a transaction) + ec52f44 (apply review: discriminating test, one-line changeset, trimmed comments) — same transactional wrap of the tool-policy create/update read-decide-write, same discriminating concurrent-creates test in policies.test.ts, same one-line changeset. The review feedback from the earlier verdict (delete policy-transactional-visibility.test.ts, add the it.live concurrent test, trim comments and changeset) was applied and merged directly; this fork PR was left open only because its branch was never updated. Rebase is moot — nothing left to carry.

@ra-co88 ra-co88 closed this Sep 13, 2026
@ra-co88

ra-co88 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Reopening: the closure was wrong. 'Superseded by main' was verified against the fork's main (ra-co88/executor), but this PR targets upstream (UsefulSoftwareCo/executor), where the fix has NOT landed — upstream/main has neither the transactional wrap in executor.ts nor the discriminating concurrent-creates test in policies.test.ts. The commits cited in the closure (9ccef8e, ec52f44) exist only on the fork. The fix content itself is correct and still wanted upstream — the review feedback (discriminating it.live test, one-line changeset, trimmed comments) was applied on this branch (f6cc5dd) and verified: lint/format/typecheck pass, 69/69 scoped tests pass. It conflicts with current upstream/main only textually; the rebase is mechanical. Apologies for the noise — the closure itself was a base-repo verification error, exactly the class of mistake this PR's review process exists to catch.

@ra-co88 ra-co88 reopened this Sep 13, 2026
@ra-co88

ra-co88 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Status update after re-verification against current upstream/main (cc0fd8f):

The mechanism half is now superseded. Upstream #1919 (caa0391, merged 2026-09-02 — after the original verdict was written) wrapped policiesCreate and policiesUpdate in transaction(...) itself. My earlier "not superseded" check grepped for this PR's comment text rather than the mechanism — that was a verification miss; the rebase conflict between two variants of the same fix is what exposed it.

What survives as this PR's unique value: the discriminating test. Upstream's policies.test.ts has no it.live concurrent-creates test — the double-position race has no regression guard upstream. This branch's it.live("concurrent creates of equally specific rules get distinct positions") is the only negative control for the wrap #1919 landed.

Proposed trim (same discipline as #1895): rebase onto current upstream/main, drop the executor.ts hunks (superseded), keep only:

  • the it.live discriminating test in policies.test.ts,
  • a one-line changeset if the maintainers want the test called out (otherwise none — test-only).

The branch will then merge clean with zero conflict surface. The earlier full-close was wrong (fix wasn't upstream); this state — mechanism upstream, test here — is what the record should show.

ra-co88 pushed a commit to ra-co88/executor that referenced this pull request Sep 13, 2026
Brings the fork up to upstream 2dc399e (Version Packages UsefulSoftwareCo#1906):
UsefulSoftwareCo#1949 workspace-write release patch, UsefulSoftwareCo#1834 selfhost Google SSO,
UsefulSoftwareCo#1947 Google OAuth listing gate, UsefulSoftwareCo#1934/UsefulSoftwareCo#1931/UsefulSoftwareCo#1933 rate-limit and
pricing, UsefulSoftwareCo#1932 pricing nav, UsefulSoftwareCo#1919 admin-restricted workspace writes,
plus release tooling and package bumps.

Conflict resolution (packages/core/sdk/src/executor.ts, policy paths):
upstream UsefulSoftwareCo#1919 landed its own transaction wrap of policiesCreate/
policiesUpdate — kept upstream's wrap verbatim and kept the fork's
discriminating it.live concurrent-creates regression test in
policies.test.ts. The fork's 8 security/hardening fixes (PRs
UsefulSoftwareCo#1886-UsefulSoftwareCo#1893) remain the fork's delta; each has a posted verdict.

Housekeeping in the same merge: .oxlintrc.jsonc ignorePatterns gains
".agents/" (local workflow files, gitignored, previously linted as
stray errors during gates). executor.ts re-run through oxfmt after
hand-resolution.

Gates: format:check, lint, typecheck green; test — package suites
green (sdk, openapi, keychain, deno-subprocess verified; full
parallel turbo run shows rotating SIGINT contention failures on this
loaded machine, each "failed" package passes in isolation).
Regression guard for the tool-policy position race: two equally
specific policies created concurrently must land distinct positions.
Upstream UsefulSoftwareCo#1919 wrapped policiesCreate/policiesUpdate in a transaction;
this pins that guarantee with a discriminating it.live test.

Verified load-bearing both directions: passes with the transaction
wrap (upstream/main), fails with the pre-UsefulSoftwareCo#1919 unwrapped executor
(duplicate positions: Set size 1 vs 2).
@ra-co88
ra-co88 force-pushed the fix/policy-writes-transactional branch from f6cc5dd to 5a4caf8 Compare September 13, 2026 08:10
@ra-co88 ra-co88 changed the title fix(sdk): wrap tool-policy writes in a transaction test(sdk): concurrent policy creates get distinct positions Sep 13, 2026
@ra-co88

ra-co88 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Trim executed per the approved proposal.

Branch force-pushed as 5a4caf841 (test(sdk): concurrent policy creates get distinct positions), rebased onto current upstream/main (cc0fd8f60):

  • executor.ts hunks dropped — superseded by upstream Restrict workspace writes to admins #1919.
  • The one discriminating it.live test kept; one file, +17 lines, no changeset (test-only).
  • PR title/body updated to the trimmed scope.

Verification performed before the push (per this project's negative-control discipline):

  • Positive: on upstream/main + this test — passes; positions distinct, both rules present.
  • Negative: pre-Restrict workspace writes to admins #1919 executor (no transaction wrap) swapped in with the test kept — fails exactly as designed: the position Set size is 1, not 2, which is the duplicate-position race the wrap prevents. The test is load-bearing in both directions, not a vacuous green.

History note for the record: the original branch's mechanism landed upstream via #1919 hours after the first verdict was written; this thread (close → reopen → trim) is documented in the earlier comments. CI on the new head will confirm; the change is test-only and touches nothing else.

@RhysSullivan
RhysSullivan merged commit e619a78 into UsefulSoftwareCo:main Sep 15, 2026
40 checks passed
mhodgson pushed a commit to airbooksio/executor that referenced this pull request Sep 25, 2026
* Self-host Geist fonts (UsefulSoftwareCo#1963)

* Self-host Geist fonts instead of loading them from Google Fonts

* Move console font test to root tests so it typechecks

* Scope tool preview compile to referenced definitions (UsefulSoftwareCo#1977)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Report executions lost to a session reset (UsefulSoftwareCo#1978)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Point agent setup CTA to the setup docs (UsefulSoftwareCo#1944)

* docs(sdk): invoke tools by address, not id (UsefulSoftwareCo#1916)

* Document connections.list result shape in skills.ts (UsefulSoftwareCo#1808)

* Fix long connection names overflowing the removal dialog (UsefulSoftwareCo#1975)

* Fit the empty Toolkits grid to the viewport (UsefulSoftwareCo#1897)

* fix(openapi): preserve vendor +json Content-Type on object bodies (UsefulSoftwareCo#1937)

* fix(oauth): retry a refresh grant without scope when the AS refuses it (UsefulSoftwareCo#1982)

Railway answers every scope-bearing refresh with `invalid_scope: refresh
token missing requested scope` when its stored grant is narrower than the
authorization it echoed back, so a connection whose refresh token was
still live failed every call as `oauth_refresh_failed` and only a hand
re-authorization recovered it.

RFC 6749 §6 defines omitting `scope` as "the scope originally granted",
so retry the grant once without it. Only `invalid_scope` qualifies:
`invalid_grant` means the token is dead, and retrying that spends a
rotating refresh token to learn nothing.

* Honor OAuth resource metadata challenges (UsefulSoftwareCo#1981)

* Cascade integration removal to every member and stop serving orphaned rows (UsefulSoftwareCo#1991)

* Cascade integration removal to every member and stop serving orphaned rows

* Register the MCP server before starting OAuth in the local app test

* fix(openapi): fetch analyticsdata Discovery from the service host (UsefulSoftwareCo#1955)

* fix(openapi): fetch analyticsdata Discovery from the service host

The central Discovery directory does not list the GA4 Data API, so
https://www.googleapis.com/discovery/v1/apis/analyticsdata/v1beta/rest
answers 404 and importing the source fails. Route analyticsdata to its
own host the way forms, keep and photospicker already are.

* Test queue timeout with a controlled clock

* Verify Analytics Data discovery import end to end

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Advertise refresh-token grant in OAuth client metadata (UsefulSoftwareCo#1974)

* Advertise refresh-token grant in OAuth client metadata

* Test OAuth metadata through connection and tool use

* Test queue timeout with a controlled clock

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(mcp): shut down scoped executor on session eviction (UsefulSoftwareCo#1917) (UsefulSoftwareCo#1971)

* fix(mcp): shut down scoped executor on session eviction (UsefulSoftwareCo#1917)

Retain the scoped Executor created for each MCP session and shut it down when an in-memory MCP session is evicted, closed, or fails eager initialization. This ensures child tool subprocesses and connection pools owned by the session are properly cleaned up.

* test(cloud): fix flaky timer precision assertion in session build semaphore test

* Verify MCP session disposal closes upstream resources

* Test queue timeout with a controlled clock

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(mcp): bound discovery connection teardown (UsefulSoftwareCo#1878)

* fix(mcp): bound discovery connection teardown

Discovery cleanup runs in an interruption-masked finalizer. Bound an unresponsive transport close so a completed tool listing cannot strand health checks or other callers indefinitely.

* test(mcp): match current discovery client contract

* test(mcp): avoid wall-clock teardown assertion

* Exercise discovery teardown with real MCP connections

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(react): honor prefers-reduced-motion in the shared stylesheet (UsefulSoftwareCo#1892)

* fix(react): honor prefers-reduced-motion in the shared stylesheet

* Test queue timeout with a controlled clock

* Cover reduced motion changes in the browser

---------

Co-authored-by: pt-act <211776491+pt-act@users.noreply.github.com>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Keep artifact deletion optimistic across navigation (UsefulSoftwareCo#1901)

* Keep artifact deletion optimistic across navigation

* Restore gallery-card delete coverage and bound the Saved artifacts wait in the artifacts e2e

* Test queue timeout with a controlled clock

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Surface unreachable OpenAPI upstreams as network errors (UsefulSoftwareCo#1960)

* Surface unreachable upstreams as network error

* Test queue timeout with a controlled clock

* Classify transport failures without hiding invocation defects

* Name the integration and origin in the unreachable-upstream message

Executor sends the request, not the user's browser, so "check your
network" pointed at the wrong place. Name the integration and the origin
that could not be reached and tell the user to verify the base URL and
that the service is online. Only the host is lifted off the transport
failure; the path, query, and headers stay out of the message.

* Record the cause of unreachable upstreams without the request

Classifying transport failures as a typed tool result took them off the
hosts' defect path, which was the only place the cause was logged. Lift
the errno-style code (ECONNREFUSED, ENOTFOUND, UND_ERR_SOCKET, ...) off
the fetch cause chain, log a warning and annotate the span with the
integration, host, and code, and return the same sanitized pair in the
tool result details. The raw TransportError stays out of details on
purpose: it carries the whole request, including resolved auth headers.

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(mcp): report completed connected tool (UsefulSoftwareCo#1964)

* fix(mcp): report completed connected tool

* Test queue timeout with a controlled clock

* Verify connected tool provenance over MCP

* Exclude upstream tool failures from completion metadata

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Return tool annotations from describe.tool (UsefulSoftwareCo#1980)

* Return tool annotations from describe.tool

`ToolSchemaView` carried everything about a tool except the annotations a
plugin declares on it, so code written inside `execute` could not tell an
approval-gated tool from a plain one except by reading its prose description.
The data was already persisted on the tool row and already used by the
executor's own approval copy.

`tools.schema` now projects the three fields declared in `ToolAnnotations`
(`requiresApproval`, `approvalDescription`, `mayElicit`), and `describe.tool`
passes them through. The fields are picked explicitly rather than spread,
because plugins keep private bookkeeping alongside the contract: the mcp
plugin stores its upstream tool name and `_meta` map in the same column, and
none of that should reach a caller.

The key is omitted when a tool declares no annotations, so the describe
payload does not grow for the tools that have nothing to say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Test queue timeout with a controlled clock

* Verify annotation projections and expose resume failure context

* Model private annotation metadata through the test adapter

* Use the static tool address for schema discovery

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(oauth): request offline_access for Vercel MCP (UsefulSoftwareCo#1951)

* fix(oauth): request offline_access for Vercel MCP

* Test queue timeout with a controlled clock

* Verify Vercel lifecycle scopes through an OAuth connection

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(cloud): require CSRF state in the WorkOS login callback (UsefulSoftwareCo#1886)

* fix(cloud): require CSRF state in the WorkOS login callback

* Test queue timeout with a controlled clock

* Exercise browser binding and replay protection for login state

* Capture provider redirect before testing callback state

* Wait for key revocation before checking authentication

---------

Co-authored-by: pt-act <211776491+pt-act@users.noreply.github.com>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Fix HubSpot optional scopes for workspace OAuth (UsefulSoftwareCo#1898)

* Fix HubSpot optional scopes for workspace OAuth

* chore: rerun flaky cloud E2E

* Honor integration-declared HubSpot optional scopes

* Test queue timeout with a controlled clock

* Verify optional OAuth scopes through consent and tool execution

---------

Co-authored-by: Lloyd Vickery <lvickery@Lloyds-MacBook-Pro.local>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Support POST health checks for HTTP RPC APIs (UsefulSoftwareCo#1952)

* Support POST health checks for HTTP RPC APIs

* Preserve existing health-check method behavior

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(e2e): honor the 503 restart envelope in the cap-eviction helper (UsefulSoftwareCo#1895)

* trim to the openSession restart-retry backstop

Per review: the concurrency half is superseded by UsefulSoftwareCo#1907 (already on
main, stricter). Rebased onto current main and kept only the retry
loop on the documented 503 restart envelope.

* Match the exact MCP restart envelope before retrying

* Test queue timeout with a controlled clock

---------

Co-authored-by: pt-act <pt-act@users.noreply.github.com>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix: use public origin for browser approval URLs (UsefulSoftwareCo#1967)

* fix: use public origin for approval URLs

CLI browser approval links ignored EXECUTOR_WEB_BASE_URL and inherited the
internal HTTP listener scheme, so TLS-proxied deployments got unreachable
http:// URLs.

* fix: skip port-0 web base URL for approval links

CLI --port 0 installs EXECUTOR_WEB_BASE_URL as http://127.0.0.1:0 before
the OS assigns a listen port. Chrome rejects that origin as ERR_UNSAFE_PORT,
so approval URLs fall back to the request origin in that case.

* Exercise browser approvals through a TLS proxy

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(oauth): complete browser callbacks before tool sync (UsefulSoftwareCo#1880)

* fix(oauth): complete browser callbacks before tool sync

Persist the refreshed OAuth grant and connection before returning the popup callback, then keep remote catalog synchronization alive through the host lifecycle. Preserve synchronous completion for programmatic callers and cover slow MCP discovery with unit and browser E2E tests.

* Verify OAuth callbacks with blocked and failing catalogs

* Release catalog gate before the separate health probe

* Verify OAuth discovery recovery through explicit refresh

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Fix hyphenated tool paths in artifacts (UsefulSoftwareCo#1884)

* test: cover hyphenated paths in browser

* refactor: narrow hyphenated artifact path fix

* Test queue timeout with a controlled clock

* Handle single-quoted artifact integration paths

* Use the catalog path for the artifact schema query

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Bridge generated-UI target=_blank links through the host openLink capability (UsefulSoftwareCo#1915)

* Bridge generated-UI `target=_blank` links through the host openLink capability

The sandbox iframe omits allow-popups, so artifact links silently failed. The
trusted renderer now relays a user click across the frame boundary, guarded by
a per-render closure nonce, and the host opens only http(s) URLs.

* Test queue timeout with a controlled clock

* Prevent generated artifacts from replacing link authorization

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(mcp): pause active timeout during elicitation (UsefulSoftwareCo#1956)

* fix(mcp): pause active timeout during elicitation

* Test approval waits beyond the MCP active-work deadline

* Test queue timeout with a controlled clock

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Add search and invoke MCP mode (UsefulSoftwareCo#1942)

* Add search and invoke MCP mode

* Preserve full annotations in MCP scenario decoding

* Return artifact source from show-artifact (UsefulSoftwareCo#1943)

* Return artifact source from show-artifact

* Preserve artifact deletion regression coverage

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Carry an approval's persistence choice through elicitation (UsefulSoftwareCo#1976)

* fix(mcp): pause active timeout during elicitation

* Carry an approval's persistence choice through elicitation

Codex Computer Use offers `persist: ["session", "always"]` in the terms of
its "Allow Computer Use to use X?" prompt and remembers the app only when
the answer names one. Executor lost the offer on the way in — the terms
projection kept strings only — and the choice on the way out, because every
adapter rebuilt the reply from `action` and `content`. So each accept was a
one-time approval and the same app prompted on every call.

- `ElicitationResponse.meta.persist` carries the choice; the vocabulary is
  closed so no host can grant more than the prompt offered.
- `approvalTerms` keeps string lists, so the offered scopes reach the host.
- The MCP plugin, the app-server bridge, and the MCP host (native mode)
  pass `_meta` through in both directions.
- The model-mode `resume` tool takes `persist`; the pause output names the
  offered scopes and says a bare accept is one-time.
- The HTTP resume API takes `persist`, and the browser approval page offers
  the scopes in a select. Nothing is chosen automatically.

Fixes UsefulSoftwareCo#1962

* Preserve approval lifetime through browser and cloud resume paths

* Test approval waits beyond the MCP active-work deadline

* Test queue timeout with a controlled clock

* Test queue timeout with a controlled clock

---------

Co-authored-by: mikemikimike <13286568797@163.com>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* fix(host-cloudflare): route toolkit MCP endpoints (UsefulSoftwareCo#1926)

* test(host-cloudflare): classify toolkit MCP paths

* fix(cloudflare): bind MCP sessions to their resource

* fix(host-cloudflare): serve toolkit MCP routes

* style: format toolkit route changes

* fix(mcp): pause active timeout during elicitation

* Carry an approval's persistence choice through elicitation

Codex Computer Use offers `persist: ["session", "always"]` in the terms of
its "Allow Computer Use to use X?" prompt and remembers the app only when
the answer names one. Executor lost the offer on the way in — the terms
projection kept strings only — and the choice on the way out, because every
adapter rebuilt the reply from `action` and `content`. So each accept was a
one-time approval and the same app prompted on every call.

- `ElicitationResponse.meta.persist` carries the choice; the vocabulary is
  closed so no host can grant more than the prompt offered.
- `approvalTerms` keeps string lists, so the offered scopes reach the host.
- The MCP plugin, the app-server bridge, and the MCP host (native mode)
  pass `_meta` through in both directions.
- The model-mode `resume` tool takes `persist`; the pause output names the
  offered scopes and says a bare accept is one-time.
- The HTTP resume API takes `persist`, and the browser approval page offers
  the scopes in a select. Nothing is chosen automatically.

Fixes UsefulSoftwareCo#1962

* Preserve approval lifetime through browser and cloud resume paths

* Test approval waits beyond the MCP active-work deadline

* Test queue timeout with a controlled clock

* Test queue timeout with a controlled clock

* Verify toolkit session isolation across resources and methods

---------

Co-authored-by: Don Pansacola <1178461+donmasakayan@users.noreply.github.com>
Co-authored-by: mikemikimike <13286568797@163.com>
Co-authored-by: Dara Adedeji <shawnadedeji@gmail.com>
Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>

* Add account discovery and guides to search and invoke (UsefulSoftwareCo#1997)

* Billing: update the payment card (UsefulSoftwareCo#1992)

* Add payment method update to billing page

* Change an existing card through the billing portal

* Recover the card button when the payment form fails to open

* Bump @executor-js/emulate to 0.14.2

* Handle payment form failures with Effect

* Hide built-in Slack OAuth option (UsefulSoftwareCo#1998)

* fix(selfhost): restrict admin area to admins (UsefulSoftwareCo#1987)

* test(sdk): concurrent policy creates get distinct positions (UsefulSoftwareCo#1893)

Regression guard for the tool-policy position race: two equally
specific policies created concurrently must land distinct positions.
Upstream UsefulSoftwareCo#1919 wrapped policiesCreate/policiesUpdate in a transaction;
this pins that guarantee with a discriminating it.live test.

Verified load-bearing both directions: passes with the transaction
wrap (upstream/main), fails with the pre-UsefulSoftwareCo#1919 unwrapped executor
(duplicate positions: Set size 1 vs 2).

Co-authored-by: pt-act <211776491+pt-act@users.noreply.github.com>

* Keep artifacts available in search and invoke mode (UsefulSoftwareCo#1999)

* Harden OAuth sessions and patch vulnerable dependencies (UsefulSoftwareCo#2000)

* Update YAML, URI, and serialization dependencies

* Harden sessions, credential traces, and runtime dependencies

* Require HTTPS for cloud outbound requests

* Normalize dependency resolution for frozen installs

* Prebundle browser telemetry before cloud development starts

* Keep seat reconciliation alive after the response (UsefulSoftwareCo#2001)

* Keep seat reconciliation alive after the response

* Await callback background work in the test fixture

* Use Effect test helpers for background work fixtures

* Normalize login return destinations (UsefulSoftwareCo#2002)

* Normalize login return destinations

* Assert parsed redirect state unconditionally

* Use public Option helpers in auth checks

* Stop capping WorkOS access token age at 24 hours (UsefulSoftwareCo#2009)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Surface MCP JSON-RPC refusals as typed tool failures (UsefulSoftwareCo#2012)

An MCP server that rejects tools/call with a JSON-RPC error (Stripe's
-32602 invalid params, for one) fell through the dispatch catch as an
opaque "Internal tool error [id]", so the model read a bad argument as
an integration outage. Carry the server's code and message on
McpInvocationError and answer with an mcp_tool_error result instead.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Hide integration edit/delete controls for non-admin members (UsefulSoftwareCo#2013)

Co-authored-by: Rhys Sullivan <rhys@rhyssullivan.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Name the SDK rejection in MCP invocation defects (UsefulSoftwareCo#2014)

* Name the SDK rejection in MCP invocation defects

An MCP tools/call rejection that is neither HTTP auth nor a JSON-RPC
error reaches the dispatch defect log as "MCP tool call failed for
<tool>" and nothing else, so an opaque correlation id cannot be traced
to a cause. Carry the SDK error class and stable code on
McpInvocationError and in its message. Structural only; the SDK message
is never copied, since a transport message can embed an upstream body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Include the HTTP status in the MCP invocation defect message

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Surface 4xx JSON refusals from MCP tools/call as typed failures (UsefulSoftwareCo#2016)

Stripe's OAuth MCP server validates the account context at the HTTP
layer: a call without stripe_context gets a 422 whose JSON body names
the missing field. That reached the sandbox as an opaque
"Internal tool error [id]" because a non-auth HTTP status was treated
as a transport defect. Read a string message out of a 4xx JSON body
(structurally; never the raw text) and answer with mcp_tool_error so
the caller can fix the arguments. 401/403 keep their auth
classification; 5xx and bodyless 4xx stay opaque.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Marketing site redesign (UsefulSoftwareCo#2011)

* marketing: homepage variants for mission, proof, captions, and agent markdown

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: sidebar rail layout variants

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: default to paper rail, fallback variant switcher

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: document-style homepage body

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: centre the document column beside the rail

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: centre rail and content in one fixed-width frame

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: no graph-paper texture in rail mode

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: hero variants for cloud, local, and self-hosted paths; gridline rail default

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: beam figure sits under the hero

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: settle on gridline rail and document body

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: five more hero variants

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: byline and signature linking to Rhys on X

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: rail CTA stack with YC badge, tabs hero default

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: remove variant picker, keep tabs hero; docker-only self-host copy

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: desktop app, not native app

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: drop MIT call-outs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: contact block in the sidebar, byline out of the hero

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: rail spy pins clicked section; contact rows are single links

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: rail spy tracks the section under a fixed reading line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: slack connect wording; remove cloudflare self-host docs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: what people say section from X posts

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: testimonials lead with strongest posts, drift starts on first view

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: slower testimonial drift

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: testimonial intro line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: add yours card in testimonials

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: consistent rail buttons; safety, run-it, and about copy from user feedback

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: restore full-bleed YC badge in rail

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: steps headline with logo reveals, beam label backing, plainer copy

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: platform-specific download buttons; drop self-referencing links

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: tab panes share one cell so switching never shifts layout

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: subhead under the steps

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: tab description holds two lines so buttons never move

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: dedicated pricing page

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: harness logos in the agent reveal; trim pricing page

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: run-it section as a four-cell grid

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: buttons inside sections keep button colours

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: allow markdown routes at the edge; lint and copy fixes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Marketing: step spacing fix and mobile layout (UsefulSoftwareCo#2017)

* marketing: keep spaces around hover words in production; mobile layout

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: centre the column when the rail is a top bar

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: centre the intro below the rail breakpoint

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Marketing: faster testimonial drift, Hermes logo (UsefulSoftwareCo#2018)

* marketing: faster testimonial drift

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: hermes agent mark in the agent reveal

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: blog on the rail layout; one shared shell for home, pricing, and blog (UsefulSoftwareCo#2019)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Marketing: port X articles to the blog, add a testimonial (UsefulSoftwareCo#2020)

* marketing: port three X articles to the blog

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: restore the three tweet links in the concepts post

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: add a testimonial

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* marketing: keep duplicated and hidden text out of find-in-page (UsefulSoftwareCo#2021)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Restart hosted invitation logins with fresh state (UsefulSoftwareCo#2023)

* Keep request resources alive for background OAuth sync (UsefulSoftwareCo#2028)

* Add MemberDirectory seam and cloud membership mirror tables (UsefulSoftwareCo#2003)

* Feed the cloud membership mirror from login and member writes (UsefulSoftwareCo#2004)

* Reconcile the membership mirror from WorkOS events (UsefulSoftwareCo#2024)

* Read members from the directory and add email search (UsefulSoftwareCo#2025)

* Authorize org membership from the local mirror (UsefulSoftwareCo#2026)

* Authorize from the mirror unconditionally; alert on a stale reconciler (UsefulSoftwareCo#2031)

* Add Hegar homepage testimonial (UsefulSoftwareCo#2038)

* marketing: one door on the homepage hero (UsefulSoftwareCo#2040)

Drop the Cloud / Desktop / Self-hosted tabs from the hero. One Start on
Cloud button, a ghost Copy setup prompt beside it, and a fine-print link to
the Run it section for the local options.

The rail stops selling: the Get started, star, and YC boxes become two quiet
text lines. Extract the intro steps and the copy button into components.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* fix(oauth): require exact explicit DCR redirect match (UsefulSoftwareCo#2032)

Treat a caller-supplied DCR redirectUri as authoritative: only a client registered with that exact callback is reused. A legacy client with no recorded redirect is kept for existing connections while a new uniquely named client is registered for the explicit callback. Callers that rely on the configured default redirect keep the previous reuse behavior.

* Fix pending changesets for a patch release (UsefulSoftwareCo#2042)

* Drop ignored host-mcp package from two changesets

* Release MCP passthrough mode as a patch

* Version Packages (UsefulSoftwareCo#2043)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Add a self-host opt-out for Better Auth rate limiting (UsefulSoftwareCo#2044)

* Fix macOS auto-update: preserve framework symlinks in the update zip (UsefulSoftwareCo#2049)

* Version Packages (UsefulSoftwareCo#2048)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Show disabled integration actions for members (UsefulSoftwareCo#2051)

* Align integration creation UI with admin permissions

* Show disabled integration actions for members

* fix(oauth): request all advertised scopes, keep sync verdicts visible (UsefulSoftwareCo#2056)

Scope discovery capped the request at 100 scopes. A resource that
advertises more (PostHog lists 150) got a token missing the scopes its
MCP server needs, so every new connection synced zero tools. Bound the
request by scope-string length (8 KiB) instead.

A credential-only health check then reported healthy over the
sync-stamped rejection, hiding the failure. Sync-supplied verdicts now
carry the tool_sync_failed reason and are served until a sync succeeds.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Protect deployment database connections (UsefulSoftwareCo#2057)

* Scope toolkit tool reads and stop gating reads on TTL-expired catalogs (UsefulSoftwareCo#2061)

* Scope tool policies to the account they are set under (UsefulSoftwareCo#2062)

* Accept Slack bot and user OAuth grants (UsefulSoftwareCo#2050)

* Accept Slack bot and user OAuth grants

* Preserve standard bearer response metadata

* Document the 20-connection application pool (UsefulSoftwareCo#2064)

* Prefer OAuth when a matching client is available (UsefulSoftwareCo#2066)

* Prefer OAuth when adding connections

* Verify authentication method selection stays usable

* Check client availability before preferring OAuth

* Pre-warm the app plane on requests that do not need it (UsefulSoftwareCo#2072)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Probe connection health through a per-connection atom (UsefulSoftwareCo#2071)

* e2e: reproduce health-probe churn on the integrations list

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Probe connection health through a per-connection atom

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* e2e: bound the churn scenario to its own connections

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* Preload console route chunks on sidebar link hover. (UsefulSoftwareCo#2081)

Co-authored-by: Cursor <cursoragent@cursor.com>

* Revert "Preload console route chunks on sidebar link hover. (UsefulSoftwareCo#2081)" (UsefulSoftwareCo#2082)

This reverts commit 315ea3c.

* Collect tax IDs in Stripe checkout (UsefulSoftwareCo#2098)

* Keep workspace-write access on cross-session MCP resume (UsefulSoftwareCo#2099)

* Require MFA to unlock cloud administration (UsefulSoftwareCo#2109)

* Test cloud admin verification and ordinary access (UsefulSoftwareCo#2110)

* Revert admin MFA requirement (UsefulSoftwareCo#2109, UsefulSoftwareCo#2110) (UsefulSoftwareCo#2115)

* Revert "Test cloud admin verification and ordinary access (UsefulSoftwareCo#2110)"

This reverts commit fec546e.

* Revert "Require MFA to unlock cloud administration (UsefulSoftwareCo#2109)"

This reverts commit d0ca1b6.

* chore: reapply fork customizations on current upstream

* fix(cloudflare): register MCP discovery routes

---------

Co-authored-by: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Nidhi Singh <nidhi2894@gmail.com>
Co-authored-by: Aarushi Singh <175547726+The-AarushiSingh@users.noreply.github.com>
Co-authored-by: yingchao <yc@yingchao.dev>
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
Co-authored-by: smrht <62819456+smrht@users.noreply.github.com>
Co-authored-by: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com>
Co-authored-by: Saatvik Arya <aryasaatvik@gmail.com>
Co-authored-by: ra-co88 <racore88.ai@gmail.com>
Co-authored-by: pt-act <211776491+pt-act@users.noreply.github.com>
Co-authored-by: Dhairyashil Shinde <93669429+dhairyashiil@users.noreply.github.com>
Co-authored-by: Davies Ayo <dadaviesayo@gmail.com>
Co-authored-by: Lloyd Vickery <115056532+LloydVickeryASI@users.noreply.github.com>
Co-authored-by: Lloyd Vickery <lvickery@Lloyds-MacBook-Pro.local>
Co-authored-by: Alp <karavil.alp@gmail.com>
Co-authored-by: pt-act <pt-act@users.noreply.github.com>
Co-authored-by: Utpal Singh <110197373+utpalsinghdev@users.noreply.github.com>
Co-authored-by: Tony(Gijung) Kim <44659712+GijungKim@users.noreply.github.com>
Co-authored-by: mikemikimike <13286568797@163.com>
Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com>
Co-authored-by: Don Pansacola <don.masakayan@gmail.com>
Co-authored-by: Don Pansacola <1178461+donmasakayan@users.noreply.github.com>
Co-authored-by: Dara Adedeji <shawnadedeji@gmail.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Rhys Sullivan <rhys@rhyssullivan.com>
Co-authored-by: Swedish Chef <142688016+SwedishChef1@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: airbooks-alpha[bot] <285858461+airbooks-alpha[bot]@users.noreply.github.com>
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.

3 participants