Skip to content

Commit d053e5f

Browse files
Brooooooklynclaude
andauthored
fix(platform): accept sandbox controllers without echoed binding props (#349)
Fixes #344 ## Problem Every managed Sandbox deploy failed at `worker_upload` with: ``` Sandbox controller authority binding differs from this deployment. ``` `provisionSandboxController` uploads the `void-sb-*` controller with a `service` binding that carries `props`, then reads `/settings` back and required the same `props`. Cloudflare applies `props` at runtime but never returns them: ``` PUT script, service binding + props → 200, target entrypoint sees ctx.props ✓ GET /settings → { entrypoint, environment, name, service, type } (no props) GET /bindings, /versions/{id} → same, no props ``` (Checked against the live Cloudflare API with throwaway scripts.) So the check rejected even a freshly created controller. The integration mock echoed uploaded bindings verbatim, which hid this. ## Fix - Drop the `props` comparison in `assertConfiguration`. Keep the `service` and `entrypoint` checks. The plain-text identity bindings (`__VOID_SANDBOX_OWNER`, `__VOID_USER_ID`, `__PROJECT_ID`, `__VOID_PROJECT_SLUG`, `__VOID_DEPLOYMENT_ID`) come from the same upload and are still checked; `SandboxControl` still verifies each admission against the database. - The Cloudflare mock now returns the real `/settings` shape (no `props`, adds `environment`). With the old assertion, the sandbox integration suite fails with the production error. Also confirmed live: `/settings` returns `namespace_id` for the controller's Durable Object binding, including with `containers`, so the next check in this path holds. ## Verification - `vp test run --project platform-api-integration platform/packages/api/test/integration/api-sandbox.test.ts`: 15/15 - `vp test run --project "platform-*"`: all pass - `@voidcloud/api` typecheck: pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent dac5dc3 commit d053e5f

2 files changed

Lines changed: 14 additions & 11 deletions

File tree

‎platform/packages/api/src/provisioning/sandbox-controller.ts‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ type WorkerSettings = {
3333
namespace_id?: string;
3434
service?: string;
3535
entrypoint?: string;
36-
props?: Record<string, unknown>;
3736
}>;
3837
};
3938

@@ -134,18 +133,12 @@ function assertConfiguration(
134133
throw new Error(`Sandbox controller binding ${name} differs from this deployment.`);
135134
}
136135
}
136+
// Cloudflare does not return service binding `props` from `/settings`. The
137+
// plain-text bindings checked above come from the same upload as the props.
137138
const control = settings.bindings.find(
138139
(binding) => binding.type === 'service' && binding.name === '__VOID_SANDBOX_CONTROL',
139140
);
140-
if (
141-
control?.service !== apiService ||
142-
control.entrypoint !== 'SandboxControl' ||
143-
!control.props ||
144-
control.props.projectId !== identity.projectId ||
145-
control.props.deploymentId !== identity.deploymentId ||
146-
control.props.userId !== identity.userId ||
147-
control.props.slug !== identity.slug
148-
) {
141+
if (control?.service !== apiService || control.entrypoint !== 'SandboxControl') {
149142
throw new Error('Sandbox controller authority binding differs from this deployment.');
150143
}
151144
namespaceId(settings);

‎platform/packages/api/test/integration/helpers.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1620,8 +1620,18 @@ export function mockCfApi(overrides: Partial<CfApiMocks> = {}): MockCfApiHandle
16201620
if (method === 'GET' && url.endsWith('/settings')) {
16211621
const name = parseScriptNameFromUrl(url);
16221622
const script = name ? state.hostScripts.get(name) : undefined;
1623+
// Cloudflare accepts `props` on a service binding upload and applies
1624+
// them at runtime, but never echoes them from `/settings`.
16231625
return script
1624-
? cfSuccess({ bindings: script.bindings })
1626+
? cfSuccess({
1627+
bindings: script.bindings.map((binding) => {
1628+
if (binding.type !== 'service') {
1629+
return binding;
1630+
}
1631+
const { props: _props, ...rest } = binding;
1632+
return { ...rest, environment: 'production' };
1633+
}),
1634+
})
16251635
: Response.json(
16261636
{ success: false, errors: [{ code: 10007, message: 'script not found' }] },
16271637
{ status: 404 },

0 commit comments

Comments
 (0)