Workflow Packages
Pre-built scenario bundles via the pkg.* code registry.
Workflow packages — pre-built scenarios
A WorkflowPackage bundles {required_custom_properties, workflows, config_schema} so that a tenant can activate a complete scenario in a single call.
Packages live as CODE in app/domain/workflow_package/registry.py — there is no database table. The manifest is static; activation writes custom_property and workflow_definition rows.
Activation semantics
- Idempotent on the uniqueness keys
(tenant_id, target_kind, property_key)and(tenant_id, business_id)respectively overwrite=truerefreshes non-builtin workflowsis_builtin=trueworkflows are NEVER overwritten (this raisesPackageActivationError)
Packages in the registry today
pkg.samaccountname— Active Directory sAMAccountName generation and writepkg.work_email— primary email template and propagationpkg.ad_username— Active Directory username conventionpkg.external_identity_classification— auto-classify external accountspkg.manager_change_notify— email on a manager changepkg.ivip_mode— legacy POC / packaging flag (de-emphasized since 2026-06-07: visibility-only is a per-object opt-in capability, not a separate product tier or default)
The list is exactly what the registry holds — nothing is hidden behind a licence or a flag. It also shrinks: pkg.externals_data was removed in July 2026 once the navigation surface it gated no longer existed as a separate, gated page, which left it activating a feature flag that nothing read.
Starter templates share the same registry
Since the process-template registry converged into this one (2026-07-07), the same registry also holds nine starter templates under a tmpl.* prefix — new hire, job change, termination, bulk-import audit, and a set of notification workflows for separation-of-duties violations, risk findings, granted access, provisioned requests and drift alerts.
They are the same manifest shape with a different kind, and one deliberate behavioural difference: a starter template always seeds its workflow disabled. It is a starting point to review and adjust in the builder before it does anything, not a scenario to switch on. Passing a different business_id creates a second copy rather than overwriting the first.
Adding a new package — three steps
- Define a
PackageSpecinregistry.pywith anidof the formpkg.<name>, abuilds(config) -> BuildResultcallable, and an optionalconfig_schema. - Append it to the
_PACKAGESregistry. No migration, no seed, and no lifespan bootstrap. - Add a unit test: one for the default build, one for the build with configuration, and one for what the activator skips versus creates.
⚠ Note
Packages may ONLY write
CustomPropertyandWorkflowDefinitionrows. No notification templates, no groups, and nothing else — keep them bounded. For other seeds, use a dedicated seeder.
Config-packs (pack.*) — the broader bundle
Alongside workflow packages, config-packs (app/domain/config_pack, id prefix pack.*) bundle more than workflows: the manifest also carries object types (SchemaDefinition), scoped roles (delegated admin — a capability set bound to an owner-anchored scope), approval rules and separation-of-duties rules. Installation is idempotent, and reports per item what it created versus kept.
What the registry holds today is six sector packs — financial services (SOX and DORA), healthcare (HIPAA and GDPR Article 9), public sector (NIS2 and the EU AI Act), mid-market (ISO 27001 and GDPR), manufacturing (IEC 62443 and NIS2 across IT and OT), and AI enterprise (EU AI Act and NIST AI RMF).
They are deliberately small. Each seeds one opinionated approval rule for its sector — routing high-risk resources to security and compliance, patient-data access to a privacy officer, AI-agent access to a four-eyes check — and the financial pack additionally seeds three separation-of-duties rules. None of them currently seeds object types or scoped roles, even though the manifest supports both: a pack is a starting point that saves an hour of first-day configuration, not a compliance programme in a box.
An earlier delegated-onboarding pack, which composed external organisations out of these primitives, was retired in July 2026: external onboarding became a first-class module that owns those objects directly, and keeping a pack that modelled the same thing in parallel would have meant two definitions of it. See external onboarding.
Further reading: