Workbench composition: the Consumes primitive

Goal: Let one workbench call another workbench's hosted mode. Declared in the workspace; wired by the platform.


What Consumes is

Every hosted workbench can declare a list of other workbenches (in the same workspace) that it depends on. Each declaration is one row:

{
  "Workbench": "api-sample",   // APISlug of the target workbench
  "Mode": "API",               // Mode key on the target (must be enabled)
  "As": "products"             // Optional alias for the injected env var name
}

Multiple rows are allowed. Each targets a specific mode of a specific workbench — the SAME target workbench can appear twice if you consume both its API and MCP modes.


What gets injected

For each declared dependency, OpenX injects two things into every per-mode container of the consumer workbench at deploy:

1. Individual env var per dependency:

WB_CONSUMED_{alias}_URL = "{workspace-origin}/oi-api/workbenches/{Workbench}/{Mode}"
  • Alias falls back to Workbench when As is unset (so a bare {Workbench: 'pricing', Mode: 'API'} yields WB_CONSUMED_pricing_URL).
  • URL is the same workspace-proxied path any human consumer would use.

2. Enumeration hook (single env var, JSON-encoded):

FATHYM_OX_CONSUMES_JSON = '{"products":"https://.../api-sample/API","pricing":"https://.../pricing/API"}'
  • Parse via JSON.parse(Deno.env.get('FATHYM_OX_CONSUMES_JSON') ?? '{}') when a consumer needs to enumerate all dependencies without knowing alias names in advance (e.g. a workbench that discovers its own connected surface at runtime).

Both patterns land on the consumer at the same time. Pick whichever fits your consumer's access shape.


How to declare Consumes

Consumes lives on the workspace record (Details.Consumes on the workbench EaC). The typical path is via the inspector:

  1. In your workspace, open the workbench that will consume another.
  2. Switch to the Consumes tab in the inspector.
  3. Click Add dependency. For each row:
    • Target dropdown: pick another workbench from the workspace (filtered to those with an APISlug set).
    • Mode dropdown: pick an enabled mode on the target.
    • Alias (optional): an env-var-friendly name; if omitted, the target's APISlug is used.
  4. Save. Redeploy the consumer workbench to pick up the injected env vars.

The inspector only lets you pick valid targets + modes, so most user error is prevented at the UX layer. On top of that, the workspace commit endpoint validates every Consumes entry before persisting — bad references return 400 { Errors: [...] }.


How to read the injected env vars

Inside the consumer workbench's mode container, Deno code reads the env vars directly:

// The most common pattern: fetch a specific dependency.
const productsUrl = Deno.env.get('WB_CONSUMED_products_URL');
if (!productsUrl) {
  throw new Error('WB_CONSUMED_products_URL not set — declare Consumes on this workbench.');
}

const res = await fetch(`${productsUrl}/hello/greet`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    // See "Outbound auth caveat" below.
    'Authorization': `Bearer ${Deno.env.get('FATHYM_OX_SERVICE_JWT')}`,
  },
  body: JSON.stringify({ arg0: 'world' }),
});

Enumeration variant:

// Discover all consumed workbenches without knowing alias names.
const consumes = JSON.parse(
  Deno.env.get('FATHYM_OX_CONSUMES_JSON') ?? '{}',
) as Record<string, string>;

for (const [alias, url] of Object.entries(consumes)) {
  // Enumerate + call each.
}

Governance

The consumer's outbound request hits the workspace proxy at ${workspaceOrigin}/oi-api/workbenches/${target}/${mode} — the same URL a human consumer would use. That means:

  • Same access-right gate. Consumer's JWT must carry Workspace.Workbench.Host. The consumer container is NOT trusted implicitly.
  • Same audit trail. Every proxied call is logged with caller (the consumer workbench's identity), target APISlug, method, and timestamp.
  • Same isolation. Cross-mode traffic is routed through the workspace's network boundary — the consumer container never gets a direct pipe to the target container.

Composition is a WIRING primitive, not a BYPASS primitive.

A workbench that consumes another workbench is just an HTTP client to the workspace proxy. No implicit trust, no service mesh, no cross-container secrets — the exact same governance model as an external caller, applied to the consumer container.


Outbound auth caveat

Thinking Tip:

Service-JWT injection is a known follow-up. The consumer container needs a JWT with Workspace.Workbench.Host to make its outbound proxy calls, but the SOP doesn't yet auto-inject one. Until it does, use one of these workarounds:

  • Manual per-workbench JWT — set FATHYM_OX_SERVICE_JWT in the workbench's Details.Env via the inspector. Short-lived; rotate manually.
  • Skip outbound auth in demos — the request 401's from the proxy, error surfaces in the consumer response, but proves the plumbing works.

Full E2E composition without manual auth requires a follow-up ticket on service-JWT injection.


Failure modes

SituationOutcome
Consumes target APISlug doesn't existCommit rejected with 400 { Errors: ["Workbench '{lookup}' declares Consumes on '{target}' which does not exist..."] }
Consumes target found but named mode not EnabledCommit rejected with 400 { Errors: ["...mode '{Mode}' which is not enabled on the target."] }
Target uninstalled AFTER consumer deployedConsumer's env var still points at the stale URL; requests to it 404 at the workspace proxy. Redeploy the consumer to re-run the SOP's D.10.3 defence-in-depth check, which throws with HostingStatus: Failed.
No Details.Consumes at allFATHYM_OX_CONSUMES_JSON = "{}"; no individual WB_CONSUMED_*_URL vars injected.

Sample

The fathym-deno/ui-workbench sample declares { Workbench: 'api-sample', Mode: 'API', As: 'products' } and reads WB_CONSUMED_products_URL in its /products route to render a page from Phase 9's api-workbench. See the sample's README for the end-to-end walkthrough.


Next steps

If you want to...Go to...
Understand WebMode (the most common consumer)Web access →
Understand the target API modeAPI access →
Install another workbenchInstall a workbench from source →
See the mode model + overviewWorkbenches overview →
On this page