-
Notifications
You must be signed in to change notification settings - Fork 2
Specs Governance Lifecycle
Status: canonical. This is the only lifecycle engine: every domain element runs on it, the first-generation
core/workflow.pyengine has been removed, and the separate approval axis (is_approved) is gone. Cross-cutting governance rules and surfaces are documented inworkflow.md.
Every domain element moves through a lifecycle: a declarative schema of steps (étapes) and the transitions between them. This is the single, standard way state is modelled, replacing the previous per-entity status fields and the first-generation workflow engine.
- Mandatory bookend steps. Every lifecycle has exactly one Draft step (the single entry point) and at least one Archived step (the exit). What sits between is entity-specific.
- Linear or cyclic. The transition graph is free-form: a straight line, a cycle, or a line with an exit. Returning to an earlier step (rework, reactivation) and leaving Archived (restore) are both allowed.
-
Free or constrained transitions. A transition declares a specific
sourcestep, orANY("from any state") - e.g. any → Archived while Draft → Validated is only legal via the explicit path. -
Form per transition. A transition may require a Django
Form; its cleaned data is recorded with the transition. -
Role / people restriction. A transition may be restricted to ISO 27001
roles (by
RoleType) and/or to named users resolved from the instance. -
Full history. Every performed transition is recorded as an immutable
LifecycleEvent(actor, from, to, comment, form data, timestamp).
Three layers, deliberately separated so the schema stays pure and testable:
-
StepKind:DRAFT,INTERMEDIATE,ARCHIVED. -
Step:code, translatablelabel,kind, the governance flags (counts_in_reports,linkable,deletable), a UItoneand a list oftriggers(see below). Helpersdraft_step()/archived_step()build the canonical bookends. -
Trigger: a behaviour fired when the entity enters a step (a transition targets it), astype+ type-specificconfig. The first type isconfirm: a Yes/No confirmation modal shown before the move, with an optionalconfig["message"](blank ⇒ the default, translated "Are you sure?"). A step may carry several triggers; unknown types round-trip untouched but are ignored by the UI, so a newer definition stays forward-compatible on an older build. New types slot in by handling thetypein the stepper - no schema change. -
Transition:target,source(a step code orANY),label,form_class(a DjangoFormclass or its dotted path),allowed_roles(a tuple ofRoleType),allowed_users(a callable(instance) → iterable),requires_comment.from_any,is_restrictedandget_form_class()are derived. -
Lifecycle: ordered steps + transitions. Validates the invariants (unique codes, exactly one Draft, ≥ one Archived, transition endpoints exist or areANY). Exposesinitial_step,transitions_from(code)(specific source + wildcards, self-target excluded),find_transition(source, target)(explicit wins over wildcard) and the governance code sets. -
Registry :
register_lifecycle()/get_lifecycle()/LIFECYCLE_REGISTRY. -
Evaluation :
user_can_perform(transition, instance, user)(open ⇒ anyone; superuser bypass; ISO role assignment scoped to the instance; or the resolved allowed users),available_transitions(...)andvalidate_transition(...)(raisesUnknownStepError/IllegalTransitionError/TransitionNotAllowedError/CommentRequiredError).
A generic (content-type) append-only log: content_type + object_id (char, so
both UUID and integer PKs work), lifecycle_name, from_step, to_step,
actor, comment, form_data (JSON), created_at. LifecycleEvent.record(...)
appends one; LifecycleEvent.for_instance(obj) returns an instance's timeline.
perform_transition(instance, target, *, user, comment, data, files, lifecycle, step_field="workflow_state", enforce_permission, save) is the single funnel for
every layer (web, DRF, MCP): it reads the current step from the instance,
validates the move (legality, role/user restriction, required comment),
validates the per-transition form when one is declared, writes the new step,
persists, and appends the LifecycleEvent. Returns (event, transition).
Restriction is by ISO 27001 role and/or named user, not by Django
permission (a deliberate departure from the legacy engine). allowed_roles
lists RoleType categories; a user passes if assigned to a context.Role of
one of those types that shares a scope with the instance (a role's authority
is bounded by its scopes). allowed_users is a callable resolved against the
instance for dynamic people (e.g. lambda obj: [obj.owner]). An unrestricted
transition is open to anyone with access; superusers always pass.
Binding a transition to a specific named role (e.g. exactly "CISO" rather than any Governance role) is the planned next extension point - a small DB mapping from
(lifecycle, transition)to concreteRolerows - layered on top of theRoleTypemechanism without changing the schema API.
- Foundation: engine +
LifecycleEvent+ service + tests. Done. - Per-entity lifecycle definitions registered in each app. Supplier and
Scope done. Supplier (
assets/lifecycles.py): the audit-proof supplier-risk lifecycle - Draft → Onboarding → Risk questionnaire → Evaluation → Compliant / Non-compliant, cycling, with Archived as the exit. Scope (context/lifecycles.py): the perimeter governance lifecycle - Draft → Definition → Validation → In force → Review (periodic, looping back to In force), with Archived as the from-any exit (and restore to Draft);in_forceandreviewcount in reports and are linkable.BaseModelroutes governance / transitions / history through the new engine whenever a model setsLIFECYCLE_NAME(Supplier.LIFECYCLE_NAME = "supplier",Scope.LIFECYCLE_NAME = "scope"). - Stepper UI reads through
LifecycleStepperMixin+includes/lifecycle_stepper.html(main flow as a connected wrapping line, available transition targets clickable, the Archived exit detached). DRF and MCP perform through the service. Stepper done. DRF/MCP done: the generictransition_<entity>/<entity>_allowed_transitionsMCP tools and theWorkflowTransitionViewendpoint branch onget_lifecycle()and route standardised-engine entities throughtransition_to/available_transitions. - Reports / KPIs / linking / deletion read the governance code sets from the
resolved lifecycle. Done:
reportable()/linkable()/deletable_states(), the list summary rail and the unified history timeline resolve a model's code set off its lifecycle when it setsLIFECYCLE_NAME, falling back to the legacy workflow otherwise. - Retire
core/workflow.pyonce every entity is migrated.
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)