feat(durable): adapter-aware decision delivery, scoped to the tenant workspace - #26
Merged
Merged
Conversation
…workspace
foreman-gk6k. Approve/Deny assumed the callback was an HTTP endpoint, so it
worked only on the Zapier adapter. lib/durable/delivery.ts is now the single
seam: routes and service code call deliverDecision and never touch
resolveCallbackUrl/postCallback directly.
- zapier: unchanged mechanics (resolve the reported URL, POST it, cancel via
the SDK). The existing service tests now mock @/lib/durable/deploy instead
of @/lib/durable, so the REAL seam runs on top of them and those assertions
keep proving the Zapier path rather than a stub.
- filesystem: no URL to POST to, so delivery goes through the store's
callback(token, payload). The token is read with getOperations(), a
lease-free read -- checkout() would take the runner's lease.
- cancel: the adapter has no cancel primitive. A suspended run holds no lease,
so take it and release as failed; if the runner IS mid-tick the lease is
refused and that is reported instead of half-cancelling.
- DecisionResult carries runStatus so callers persist what the adapter really
reported, not an assumed cancelled.
State lives at <workspace_dir>/.durable, per tenant, matching the agent
workspace layout. FilesystemClient is constructed with an explicit baseDir
rather than createClient(), because createClient reads PROCESS-GLOBAL config
that cannot be per-tenant on a server serving several workspaces at once.
cancelRunForUser went through the seam too -- it had the same hardcoding.
Two traps found by measurement and now guarded:
- configureDurable imported from @zapier/zapier-durable/node reports success
via its own getConfig() while the runtime keeps writing to the default
fsDir. The subpath and the root hold SEPARATE config state. Import from the
root only.
- The option key is fsDir; an unknown key (e.g. filesystem: {baseDir}) is
silently ignored and falls back to ~/.config/zapier-sdk/durable. An earlier
revision of these tests wrote into the real home directory because of it.
There is now a regression test asserting nothing is written there.
Filed foreman-1uz7: node:fs co-location with the workspace holds only while
the workspace FS is local, or while the durable runs inside the sandbox mount.
422 tests pass (17 new, incl. a real offline durable), typecheck 0, lint 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
foreman-2qbk (part 2). humanApprovalGate() emitted a __report_callback_url_* step for one reason: getDurableRun will not expose the callback URL, so the durable had to smuggle it out through a step result. That does not apply locally. The run is in-process, so Foreman reads the token straight off the execution's operations via findOpenLocalGate. On the filesystem adapter the gate now emits neither the reporting step nor the URL binding — an unused binding would be dead weight in generated source — while the Zapier path is byte-identical to before. Adapter comes from activeDurableAdapter() and is overridable per call, so tests pin it explicitly instead of depending on ambient env. 424 tests pass, typecheck 0, lint 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Approve/Deny assumed the callback was an HTTP endpoint, so it only ever worked on the Zapier adapter.
lib/durable/delivery.tsis now the single seam — routes and service code call it and never touchresolveCallbackUrl/postCallbackdirectly.Builds on #25.
Behaviour
client.callback(token, payload). The token is read withgetOperations(), a lease-free read;checkout()would take the runner's lease.DecisionResult.runStatuscarries what the adapter actually reported, so callers persist the truth instead of assuming"cancelled"— a run can finish before a cancel lands.cancelRunForUserwent through the seam too; it had the same hardcoding.Tenancy
State lives at
<workspace_dir>/.durable, per tenant, matching the agent workspace layout.FilesystemClientis constructed with an explicitbaseDirrather thancreateClient(), becausecreateClientreads process-global config that can't be per-tenant on a server handling several workspaces at once.DurableCallOptions.clientmakes this per-call with no global state.Approval gate (foreman-2qbk, part 2)
humanApprovalGate()emitted a__report_callback_url_*step solely to smuggle the URL pastgetDurableRun. That doesn't apply locally, so on the filesystem adapter it emits neither the step nor the URL binding. Zapier path byte-identical.Two traps, found by measuring
configureDurableimported from@zapier/zapier-durable/nodereports success via its owngetConfig()while the runtime keeps writing to the defaultfsDir— the subpath and the root hold separate config state. Import from the root only.fsDir. An unknown key (e.g.filesystem: {baseDir}) is silently ignored and falls back to~/.config/zapier-sdk/durable. An earlier revision of these tests wrote into a real home directory because of it. There's now a regression test asserting nothing is written there.Tests
Existing service tests now mock
@/lib/durable/deployinstead of@/lib/durable, so the real seam runs on top of them and those assertions keep proving the Zapier path rather than a stub.424 tests pass (19 new, including a real offline durable end-to-end), typecheck 0, lint 0.
Closes
foreman-gk6k.foreman-2qbkstays open, blocked onforeman-3uje(sandboxed local execution).🤖 Generated with Claude Code