workspace block sets workspace-wide defaults for engine, runtime, backend, providers, tags, hooks, plugins, and feature toggles. It applies to every stack in the compilation unit unless a more-specific declaration overrides a field.
Multiple workspace blocks may appear across .xcl files in the same directory. The blocks are independent — each can carry a match {} sub-block to restrict which stacks it applies to. When no match {} is present, the block is universal: it applies to all stacks.
Syntax
workspace takes no label — there is no quoted string after the keyword. The human-readable name for the workspace comes from the name field inside the body.
Only one backend {} sub-block is allowed per workspace {}. Duplicate backends produce a hard parse error.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
name | string | "" | Human-readable workspace name |
engine | string | "native" | Execution engine (see below) |
runtime | string | "xcl" | Codegen runtime (see below) |
engine
Controls which execution engine processes the stack.| Value | Description |
|---|---|
"native" | Built-in ubx engine (default) |
"pulumi" | Pulumi execution plugin |
"terraform" | Terraform execution plugin — not yet implemented, produces a hard error at ship time |
runtime
Controls which code generation target is used.| Value | Description |
|---|---|
"xcl" | XCL native — no codegen step (default) |
"python" | Pulumi Python |
"typescript" | Pulumi TypeScript |
"go" | Pulumi Go |
Valid engine / runtime combinations
Not all engine/runtime pairs are valid. The typechecker enforces these rules:| engine | Valid runtimes |
|---|---|
native | xcl only |
pulumi | python, typescript, go |
terraform | xcl only |
match sub-block
Thematch {} sub-block turns a universal workspace into a label-scoped workspace. A label-scoped workspace only applies to stacks whose resolved labels (from their meta {} block and the matched context {}) contain all the key-value pairs declared in match {}.
env = "prod", this workspace block applies on top of any universal workspace. Fields from the scoped workspace override the universal workspace for that stack.
A workspace block with no match {} is universal — it applies to every stack and is processed first in the resolution chain.
backend sub-block
Declares the Pulumi state backend. See backend for the full reference.provider sub-block
Declares provider defaults applied to all stacks that inherit this workspace.tags sub-block
Key-value pairs applied as resource tags for all stacks that inherit this workspace. Tags merge additively across the resolution chain — a tag set at the workspace level is not removed by a more specific block; it is extended.hooks sub-block
Shell commands to run before or after aubx ship or ubx plan operation.
| Field | Timing | On failure |
|---|---|---|
before_ship | Before the deploy starts | Abort the deploy (fail-fast) |
after_ship | After a successful deploy | Log error, do not fail the operation |
before_plan | Before the plan starts | Abort the plan (fail-fast) |
after_plan | After a successful plan | Log error, do not fail the operation |
sh -c "<command>" in the stack directory. On a non-zero exit code, before_* hooks immediately abort the operation; after_* hooks log the error and continue.
Hooks merge additively across the resolution chain — hooks from workspace, context, and meta are concatenated into a single list in resolution order.
plugins sub-block
Declares plugins to load for stacks that inherit this workspace.- A name (bare identifier — use underscores, not hyphens:
my_scannernotmy-scanner) - A
sourcefield specifying where to fetch the plugin
Plugin source schemes
| Scheme | Example | Description |
|---|---|---|
strata:// | strata://scanners/my-scanner | Strata plugin registry |
github:// | github://ubiquex/cost-estimator | GitHub repository |
file:// | file://./tools/linter | Local filesystem path |
ai sub-block
scan sub-block
telemetry sub-block
history sub-block
Multiple workspace blocks
Multipleworkspace blocks are allowed in the same compilation unit. They are merged per-field during resolution — a label-scoped workspace does not replace the universal workspace; it overrides only the fields it declares.
env = "prod" sees the prod-scoped backend. A stack without that label sees the default backend. Both stacks see tags.project = "platform" from the universal block.
Where workspace blocks live
workspace {} blocks can appear in any .xcl file. There is no required filename — discoverable in workspace.xcl, config.xcl, or any other file in the stack directory.
Resolution chain
The full resolution order is:- Universal
workspace {}(nomatch {}) — lowest priority - Label-matched
workspace {}— fields override step 1 context {}named by the stack’smeta { context = "..." }— fields override step 2meta {}insidestack {}— highest priority

