-
Notifications
You must be signed in to change notification settings - Fork 2
SDK Lifecycle
Every domain record runs a registered lifecycle. It is the platform's governance backbone : the step a record sits on decides whether it counts in reports, whether other objects may link to it, and whether it may be deleted at all.
The canonical contract is governance/workflow.md; the engine internals are governance/lifecycle.md; the shipped lifecycles are listed in reference/generated/lifecycles.md. This page is how you add one.
No, usually. A model that inherits BaseModel and declares no LIFECYCLE_NAME
runs the default four-step lifecycle : Draft, Pending validation, Validated,
Archived. That is the right answer for anything whose only governance question
is "has this been approved".
Declare a specific lifecycle when the entity has operational stages that mean something to the business : an incident that is detected, then triaged, then contained; a contract that is active, then expired.
A step carries governance metadata:
| Field | Effect |
|---|---|
code |
The stored value in workflow_state
|
label |
Shown in the stepper and the badge. Lazy-translated |
kind |
DRAFT (exactly one, the entry), INTERMEDIATE, or ARCHIVED (at least one, the exit) |
counts_in_reports |
Read by reportable() : dashboards, KPIs, reports |
linkable |
Read by linkable() : the object pickers of other forms |
deletable |
Read by deletable_states() : whether deletion is offered |
tone |
The badge colour |
triggers |
Behaviours fired on entering the step, currently a confirmation modal |
A transition is a permitted move:
| Field | Effect |
|---|---|
source / target
|
Step codes; source=ANY means "from any state" |
label |
The button text |
requires_comment |
Forces a comment, recorded on the event |
permission_action |
A permission suffix required to perform it, built against the instance's workflow_perm_namespace
|
allowed_roles |
Restricts it to users holding an ISO 27001 role, scoped to the instance |
allowed_users |
A callable (instance) -> iterable[user] for dynamic restriction |
form_class |
A form collected when performing it; its cleaned data lands on the event |
The transitions live in <app>/constants.py and the lifecycle is generated
from them, so the constants stay the single source of truth and the interface,
the API and the MCP layer cannot disagree about what is possible.
# assets/constants.py
ATTESTATION_STATES = [
# (code, label, counts_in_reports, linkable, deletable, is_initial, is_terminal, tone)
("draft", _("Draft"), False, False, True, True, False, "neutral"),
("active", _("Active"), True, True, False, False, False, "success"),
("expired", _("Expired"), False, False, False, False, True, "warning"),
("archived", _("Archived"), False, False, False, False, True, "muted"),
]
ATTESTATION_TRANSITIONS = [
# (source, target, label, requires_comment, permission_action)
("draft", "active", _("Activate"), False, "approve"),
("active", "expired", _("Expire"), False, ""),
("active", "archived", _("Archive"), True, "approve"),
]Use a tuple or a list, never a set, for anything the lifecycle builder iterates. A set's iteration order varies between processes, which makes the declared lifecycle differ from one run to the next.
# assets/lifecycles.py
from core.lifecycle import lifecycle_from_state_flags, register_lifecycle
ATTESTATION_LIFECYCLE_NAME = "supplier_attestation"
ATTESTATION_LIFECYCLE = register_lifecycle(
lifecycle_from_state_flags(
ATTESTATION_LIFECYCLE_NAME,
ATTESTATION_STATES,
ATTESTATION_TRANSITIONS,
layout="graph",
)
)# assets/models/supplier_attestation.py
class SupplierAttestation(ScopedModel):
LIFECYCLE_NAME = "supplier_attestation"
REFERENCE_PREFIX = "SATT"lifecycle_from_state_flags auto-wires a draft entry and an archived exit
when you leave them out. The edges it generates carry no
permission_action and no requires_comment, and any transition with an
empty permission_action is open to anyone who can update the record.
Left generated, a lifecycle therefore exposes an
archive -> restore -> delete path out of its deletable draft step. On a sealed
evidence artefact that means an A.5.28 record could be destroyed by anyone
holding update. Declare draft and archived yourself, with gated edges, as
the incidents module does.
# assets/apps.py
class AssetsConfig(AppConfig):
def ready(self):
from assets import lifecycles # noqa: F401Omitting this import fails silently : lifecycle_name_for falls back to the
default four-step lifecycle with no error, in tests as well as in production.
Assert the binding in a test, the way incidents/tests/test_lifecycles.py does:
def test_model_resolves_its_lifecycle():
assert resolve_lifecycle(SupplierAttestation).name == "supplier_attestation"This is the rule the whole design exists to serve.
# wrong: the report breaks the day a step is added
qs.filter(workflow_state="validated")
# right: the report follows the lifecycle's own governance
reportable(qs)The same goes for linkable() in pickers and deletable_states() in deletion
logic. Get this right and adding a step to a lifecycle touches nothing else;
get it wrong and every report is a place the new step is silently missing.
A detail page gets the stepper by adding LifecycleStepperMixin to the
DetailView and including the shared template:
{% include "includes/lifecycle_stepper.html" %}It renders done, current, next and future steps, the permission-aware next step,
refusal and rework through clickable earlier pills, the archived off-ramp and
the comment modal for transitions that require one. State badges use
{% workflow_badge obj %}.
Never use a plain button or a status select for a transition, and never write per-page stepper markup. Both reintroduce, on one page, the divergence the engine exists to prevent.
Adding a step or a transition is safe : resolve_layout-style merging does not
apply here, but nothing hardcodes a step, so the new one is picked up by the
governance helpers automatically.
Removing or renaming a step is a data migration. Existing rows hold the old
code in workflow_state, and the lifecycle will refuse to resolve them. Migrate
the data in the same change.
Administrators can override a lifecycle from /config/lifecycles/, so the
code-declared version is the default rather than the last word. The generated
reference documents the shipped defaults.
- States and transitions declared in
<app>/constants.py, in an ordered container -
draftandarchiveddeclared explicitly, with gated edges - Lifecycle registered in
<app>/lifecycles.py -
<app>/apps.pyready()imports it - Model declares
LIFECYCLE_NAME - A test asserts the model resolves the lifecycle it declares
- No code compares
workflow_stateto a literal - Detail page uses
LifecycleStepperMixinand the shared stepper - Labels translated into French
-
generate_docsre-run and committed
Built from docs/ at v0.36.0. Edits made here are overwritten by the next release : open a pull request against the source instead.
- Administration
- Ask Cairn
- Assets and suppliers
- Compliance
- The dashboard
- Finding your way
- Getting started
- Incidents
- How records move
- Organisational context
- Reports and management review
- Risks
- Trust Center
- Architecture
- Configuration
- Contributing
- The documentation system
- Installation
- Internationalisation
- Operations
- Release process
- Security
- Testing
- Adding an assistant provider
- Adding a dashboard widget
- Adding a domain entity
- Declaring a lifecycle
- Adding an MCP tool
- Adding a REST endpoint
- Adding a report
- Interface conventions
- Dashboard widgets
- Lifecycles
- MCP tools
- MCP tool parameters : Assets
- MCP tool parameters : Compliance
- MCP tool parameters : Governance and context
- MCP tool parameters : General
- MCP tool parameters : Incidents
- MCP tool parameters : Reports and management review
- MCP tool parameters : Risks
- MCP tool parameters : System and administration
- MCP tool parameters : Trust Center
- Management commands
- Models
- Permissions
- REST endpoints
- Environment variables
- MCP server
- REST API
- Assistant module (Ask Cairn)
- Module 0: User Management and Access Control
- Module 1: Context and Organization
- Module 2: Asset Management
- Module 3: Compliance
- Module 4: Risk Management
- Module 4 bis - EBIOS Risk Manager
- Module 5 : Trust Center
- Module 6 : Security Incident Management
- Management review : ISO 27001:2022 compliance (clause 9.3)