Currency / Bootstrap¶
Purpose¶
The Bootstrap layer composes and publishes a Currency service stack for resolution.
It defines how a service is:
- Discovered (scene-local)
- Composed (decorators)
- Published (resolver override)
This layer controls wiring and lifecycle, not behaviour.
Usage Guidance¶
Bootstrap is responsible for:
- Composing a currency service stack from building blocks
- Publishing the composed service via
CurrencyResolve - Managing scoped lifecycle for resolver overrides
- Providing a default composition pipeline
What Lives Here¶
CurrencyBootstrap¶
Low-level publishing helper.
public static IDisposable Publish(ICurrencyService service)
- Sets the active resolver override
- Returns a scope that clears it on
Dispose()
CurrencyServiceBootstrap¶
Scene component that builds and publishes the service stack.
Execution order:
[DefaultExecutionOrder(-499)]
Behaviour:
-
On
Start(): -
Finds a
SceneCurrencyService - Warns if no
ICurrencyAuthorityis present - Composes the service stack
-
Publishes via
CurrencyBootstrap -
On
OnDisable/OnDestroy: -
Disposes the scope (clears override)
While nothing is published, resolution is ungated
CurrencyResolve falls back to the raw SceneCurrencyService whenever the override slot is empty: before Start on the first frame, for as long as the bootstrap is disabled, and after it is destroyed. The raw service has no authority, no caps, no audit and no idempotency, and it refuses nothing — Currency's fail-closed guarantee is scoped to "when using an authority-wrapped service", and this is the window in which you are not.
Disable the bootstrap only when no mutation can run. A loading screen is fine; a disabled bootstrap in a live scene is an open wallet.
Two publishers: last wins, and the first dispose clears the slot for both
There is one override slot. A second Publish displaces the first, and whichever scope is disposed first empties the slot — so tearing down bootstrap B leaves bootstrap A alive, enabled, and no longer published, with every later resolve landing on the raw service. Nothing republishes on A's behalf, because nothing tracks who owns the slot.
Publishing over a live override logs a warning in the editor naming both stacks. That is the signal you are in this shape — and it is #if UNITY_EDITOR, so a player build and a dedicated server get no signal at all. Where the failure shows up is a silently ungated wallet.
The rule is once per process, not once per scene. Step 0 of resolution is the published override, checked before any scene is consulted, so the slot is process-global. Publishing once per scene in a two-scene process produces exactly the two publishes this warning is about — two additively loaded worlds, or two matches on one dedicated server, and every resolve in both returns whichever published last: its authority context, its caps, its audit ring, its idempotency window.
So the composition model is one simulated world per process. If you need two, the second needs its own process. Authority resolution is genuinely per-scene (CurrencyAuthority keys its cache by Scene); it is service publication that is not. CurrencyBootstrapOneWorldPerProcessTests pins this, and no shipped sample scene has two.
Important Notes¶
- Bootstrap does not implement business or mutation logic
- Behaviour is defined by the composed service stack
- Authority must be present when authority decorators are composed
- Resolver overrides are global and should be scoped carefully
- Multiple bootstraps require intentional ordering
Usage Guidance¶
Resolver override¶
Currency resolution is performed via CurrencyResolve.
Publishing a service ensures that all consumers resolve the same active instance.
The override remains active until the returned scope is disposed.
Scoped publishing¶
var scope = CurrencyBootstrap.Publish(service);
- The service becomes the active resolver override
- Disposing the scope clears the override — any scope, including one whose service a later publish has already replaced. The slot has no owner
Default composition pipeline¶
The built-in bootstrap composes:
SceneCurrencyService
→ Caps + Audit + Authority
→ Idempotency
→ BatchEvents
RequireEscrow may also be included depending on policy.
Order is intentional:
- Caps run before audit
- Authority gates mutations
- Idempotency wraps outer mutations
- Batch emits grouped events
Custom bootstrap example¶
public class MyBootstrap : MonoBehaviour
{
private IDisposable _scope;
void Awake()
{
var inner = FindFirstObjectByType<SceneCurrencyService>();
if (!inner) return;
ICurrencyService svc = inner;
svc = CurrencyFactories.WithAudit(svc);
svc = CurrencyFactories.WithCaps(svc, policy);
svc = CurrencyFactories.WithAuthority(svc, this);
_scope?.Dispose();
_scope = CurrencyBootstrap.Publish(svc);
}
void OnDisable()
{
_scope?.Dispose();
_scope = null;
}
}
Always compose from the inner service, not from CurrencyResolve, to avoid stacking decorators multiple times.
Internal Use Only¶
This folder handles composition and publishing only.
Do not:
- Add gameplay logic
- Implement mutation rules here
- Depend on resolver overrides for application logic
Safe to Remove¶
This folder may be replaced if a custom bootstrap solution is used.
Removing it requires providing an alternative service composition and publishing strategy.
Related Documentation¶
- Abstractions — core contracts
- Internal — decorator implementations
- Authority — mutation gating
- Policies — caps and transfer rules
- UnityIntegration —
SceneCurrencyService