Orkestia
Blog
Build apps

Modules and feature flags

Three separate controls decide whether a signed-in user can use a part of your app — exposure, capability and feature flag — and how each one fails.

A module is a part of your app you can turn on or off for users (invoices, alerts, an AI summary). Three controls decide whether one is available. They answer different questions, and they fail differently.

ControlQuestion it answersSet with
ExposureMay an end-user token start this composition at all?identity.app.expose-virtual-workflow, identity.app.unexpose-virtual-workflow
CapabilityMay this person, in this workspace, do this verb? ({namespace}:{verb})Workspace roles and groups
Feature flagDoes this app have the module switched on?policy.feature-flag.apply, delete, query

Feature flags

A flag switches a composition or a module off for end-users without unexposing it.

policy.feature-flag.apply
  identity_app_uuid   the app
  flag_key            the composition name, or a module slug
  enabled             true | false
  flag_type           "workflow" | "module"   (default "module")
  • Applying the same app, type and key again updates the flag. It does not duplicate it.
  • Only an explicit off denies. A key with no flag is not gated.
  • A workflow flag is keyed on the composition name. Starting it as an end-user while the flag is off returns END_USER_WORKFLOW_DISABLED.
  • The decision workflow policy.decision.evaluate takes feature_flag or module in its context and denies with flag_off.

How each control fails

The three are deliberately not uniform:

ControlIf the lookup errors
ExposureDenies. It is the boundary.
CapabilityAllows, so a store hiccup cannot take every end-user offline.
Feature flag when starting a workflowAllows, for the same reason.
Feature flag in policy.decision.evaluateDenies when the flag store is unavailable.
A flag only gates the paths that check it: starting an exposed composition and the decision workflow. It is not a data boundary. Gate data with App Data ownership, not with a flag.
Making all three planes apply to App Data CRUD and the serving path is a proposed decision, not shipped behavior (ADR-IDENTITY-011). Until then, treat exposure plus ownership as the data boundary.